Compare commits
30 Commits
v1.1.0
..
754af4134a
| Author | SHA1 | Date | |
|---|---|---|---|
| 754af4134a | |||
| 118eca0948 | |||
| 3c336e0a57 | |||
| cea473b654 | |||
| 48ee413c9b | |||
| 84b75ee6b5 | |||
| 295e4c4d4a | |||
| a58d13362a | |||
| 23431c4a7a | |||
| c0de9effc8 | |||
| bd19816e91 | |||
| 5d0bd97954 | |||
| 334491db4e | |||
| 39944e072b | |||
| 9e90f88a15 | |||
| 99dea608d8 | |||
| b4e6898113 | |||
| 024b55a2a8 | |||
| da5950e872 | |||
| 1ac7f0299f | |||
| 9026de22e5 | |||
| 8ef3b98634 | |||
| 05078ba961 | |||
| 08baed9677 | |||
| 9dd5758973 | |||
| 77547bb437 | |||
| 4bc7bd7beb | |||
| 6dcfb65686 | |||
| fef1380d3a | |||
| ceeef351fd |
@@ -0,0 +1,43 @@
|
||||
# Build and deploy Jekyll site to GitHub Pages
|
||||
name: Deploy to Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Build with Jekyll
|
||||
uses: actions/jekyll-build-pages@v1
|
||||
with:
|
||||
source: ./docs
|
||||
destination: ./_site
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -1,3 +1,4 @@
|
||||
/target
|
||||
/REFERENCES
|
||||
/EXPORTS
|
||||
/scripts
|
||||
@@ -2,6 +2,63 @@
|
||||
|
||||
All notable changes to KAT are documented here.
|
||||
|
||||
## [1.3.0] - 2026-06-23
|
||||
|
||||
### Added
|
||||
|
||||
- **14 new protocol decoders** (total now 32), ported from the [ProtoPirate](https://protopirate.net/ProtoPirate/ProtoPirate) reference and the [Flipper-ARF](https://github.com/D4C1-Labs/Flipper-ARF) firmware:
|
||||
- **ProtoPirate (8):** **Kia V7** (Manchester 250/500, header 0x4C + CRC8), **Ford V1** (Manchester 65/130, descramble + CRC16/CCITT), **Ford V2** (Manchester 200/400, 0x7FA7 sync), **Ford V3** (Manchester 240/480, plaintext, decode-only), **Chrysler V0** (PWM, seed-XOR transform; Dodge/Jeep), **Honda Static** (FM, XOR checksum), **Honda V1** (PWM/PPM, CRC-fold), **Land Rover V0** (differential Manchester, 3-bit check polynomial).
|
||||
- **Flipper-ARF (6):** **Toyota** (dual PWM + NRZ variants, KeeLoq, decode-only; Lexus), **Land Rover RKE** (PWM, KeeLoq), **Mazda Siemens** (Siemens XOR/interleave), **BMW CAS4** (Manchester, 0x30/0xC5 markers, decode-only), **Porsche Cayenne** (PWM, VAG rolling-register cipher; adds an encoder to the shared Touareg air protocol), **PSA2 / "PSA OLD"** (Manchester, TEA with bounded brute-force gated behind a structural/checksum check).
|
||||
- **Real-capture validation** — decoders verified against `IMPORTS/` samples where they exist: **Ford V3** (LDV T80), **Honda Static** (Honda), **Toyota** (Camry NRZ variant), **PSA2** (Groupe PSA, recovered serial 0x99EB25 + rolling counter). The rest are validated by in-module encode→decode round-trip and synthetic-frame unit tests.
|
||||
- **Metadata, make-suggestion, and docs** — each new protocol is tagged with Encoding / RF (AM/FM) / Encryption in the signal detail panel, mapped to a Make suggestion (Honda/Acura, Chrysler/Dodge/Jeep, Toyota/Lexus, Land Rover, BMW, Porsche, Mazda, Peugeot/Citroën), and documented under `docs/`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **KeeLoq decrypt debug-panic** — `keeloq_common::keeloq_decrypt` computed `15 - r` (with `r` up to 527), underflowing and panicking in debug builds / `cargo test`; now uses `wrapping_sub` to match the reference's unsigned wraparound (release behavior, which silently wrapped, is unchanged).
|
||||
|
||||
### Notes
|
||||
|
||||
- All new decoders are gated (CRC / checksum / fixed markers / frame structure) so they do not false-match existing protocols — every previously-decoding `IMPORTS/*.sub` capture decodes unchanged. Where two decoders share a wire protocol (Porsche Cayenne ↔ Touareg; Mazda Siemens ↔ Mazda V0; Toyota variant-A ↔ Kia V3/V4 KeeLoq), the pre-existing decoder keeps first-match priority.
|
||||
|
||||
## [1.1.3] - 2026-02-20
|
||||
|
||||
### Added
|
||||
|
||||
- **Command field** — Capture metadata (press **i**) and .fob export now include **Command** (e.g. Unlock, Lock, Trunk, Panic). Export filename for unknown protocol uses Year_Make_Model_Region_Command; 8-hex suffix is shown in the filename field and saved in uppercase (e.g. `…_A1B2C3D4.fob`). .fob vehicle info and import support optional `command`.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Unknown signals** — Shown by default (`research_mode` default is now `true`). Config comment and storage docs clarify that no keystore directory is used or created (keys are embedded).
|
||||
- **Signal separation** — End-of-signal gap increased from 20 ms to **80 ms** so one button press (multiple bursts with 25–50 ms gaps) produces a single capture instead of 3–4.
|
||||
- **Short signals** — Demodulator now emits captures with **5+** level-duration pairs (was 10), so short or weak unknown keyfob bursts are no longer dropped (RSSI spike but no capture).
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Export filename** — Unknown-protocol .fob exports always get the 8-hex suffix (including when filename ends with `Unknown`); suffix is uppercase.
|
||||
|
||||
---
|
||||
|
||||
## [1.1.2] - 2026-02-20
|
||||
|
||||
### Added
|
||||
|
||||
- **Vulnerability database (CVE)** — Expanded CVE coverage: unique `id` per vuln row; year range (year_start/year_end) and arrays for makes/models; source URL per CVE. New/updated CVEs: **CVE-2022-38766** (Renault ZOE), **CVE-2022-27254** (Honda Civic), **CVE-2022-37418** (RollBack, Honda/Hyundai/Kia/Nissan — per-model year ranges), **CVE-2022-36945** (RollBack, Mazda, three consecutive signals through 2020), **CVE-2019-20626** (Honda/Acura static-code replay; confirmed vehicles per Unoriginal-Rice-Patty). Vulnerability panel shows NVD source link for each match. README table of CVEs with NVD links.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Commands** — `:replay`, `:lock`, `:unlock`, `:trunk` (and `:panic`) accept multiple IDs: single (`1`), comma list (`1, 3, 5`), range (`1-5`), executed in order.
|
||||
- **RSSI bar** — Shows **TX** with red bar and border while transmitting; draw-before-transmit so TX state is visible.
|
||||
|
||||
---
|
||||
|
||||
## [1.1.1] - 2026-02-13
|
||||
|
||||
### Changed
|
||||
|
||||
- **UI updates** — Vulnerability panel (green border when vuln found, green “encryption broken” text); signal action menu shows TX Lock/Unlock/Trunk/Panic only for encoder-capable captures (unknown/decoded get Replay only).
|
||||
|
||||
---
|
||||
|
||||
## [1.1.0] - 2026-02-13
|
||||
|
||||
### Added
|
||||
|
||||
Generated
+1
-3
@@ -444,7 +444,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kat"
|
||||
version = "1.0.2"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atty",
|
||||
@@ -482,8 +482,6 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
[[package]]
|
||||
name = "libhackrf"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e640122f67f490129cd3ab1517149b4091eeca0a14de3df7510626df258196b9"
|
||||
dependencies = [
|
||||
"num-complex",
|
||||
]
|
||||
|
||||
+5
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "kat"
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
edition = "2021"
|
||||
description = "Keyfob Analysis Toolkit - HackRF/RTL-SDR signal capture, decode, and transmit (HackRF only)"
|
||||
authors = ["KAT Team"]
|
||||
@@ -54,6 +54,10 @@ atty = "0.2"
|
||||
[build-dependencies]
|
||||
pkg-config = "0.3"
|
||||
|
||||
[patch.crates-io]
|
||||
# Vendored with ARM fix: c_char is u8 on ARM, so the upstream transmute fails
|
||||
libhackrf = { path = "vendor/libhackrf" }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
Filetype: Flipper SubGhz RAW File
|
||||
Version: 1
|
||||
Frequency: 434620000
|
||||
Preset: FuriHalSubGhzPresetOok650Async
|
||||
Protocol: RAW
|
||||
RAW_Data: 133 -1326 65 -334 99 -1786 329 -100 525 -132 457 -132 293 -66 15954 -12600 67 -1358 131 -466 133 -1958 133 -234 199 -232 65 -164 197 -100 65 -98 397 -15194 165 -692 197 -534 199 -960 97 -662 299 -166 267 -134 65 -66 131 -132 329 -66 297 -66 25421 -9832 67 -1396 65 -828 267 -100 265 -132 263 -100 161 -756 97 -558 65 -430 361 -200 131 -100 99 -132 261 -196 787 -66 66153 -13020 97 -626 163 -886 65 -928 65 -68 131 -266 829 -132 265 -66 131 -100 199 -168 2355 -12266 129 -396 131 -366 99 -828 97 -626 65 -1186 129 -66 131 -64 163 -66 131 -200 265 -166 65 -132 99 -100 199 -564 2025 -132 41605 -11144 97 -98 163 -1224 65 -1218 65 -1120 129 -132 295 -134 361 -166 297 -66 431 -100 3719 -6566 97 -330 65 -492 129 -560 65 -986 65 -624 197 -132 293 -166 991 -196 1991 -66 4519 -12294 99 -100 131 -2518 65 -166 131 -400 165 -560 231 -266 327 -130 65 -66 1561 -66 467 -15456 131 -594 97 -562 67 -2256 167 -66 167 -100 167 -100 335 -166 97 -66 231 -134 10819 -4628 67 -926 135 -628 133 -302 165 -364 133 -100 131 -366 97 -432 99 -568 199 -402 527 -132 393 -98 165 -430 199 -134 199 -66 563 -66 6381 -14148 265 -362 97 -1626 561 -100 131 -200 497 -132 431 -100 733 -15712 65 -1356 97 -500 99 -1294 133 -98 133 -132 99 -100 495 -134 759 -5594 99 -920 97 -132 65 -1282 99 -432 131 -266 65 -132 229 -234 367 -134 199 -66 265 -68 99 -132 231 -68 165 -270 99 -132 14185 -11690 99 -268 133 -168 899 -264 197 -266 97 -864 65 -796 1029 -398 495 -230 7425 -12524 229 -996 97 -332 197 -1460 163 -460 631 -100 261 -100 595 -66 131 -100 1259 -132 33529 -12536 65 -764 99 -398 65 -366 131 -134 133 -1594 231 -200 229 -68 397 -66 199 -134 265 -362 299 -100 12961 -12204 65 -336 65 -298 65 -830 133 -132 133 -232 163 -334 131 -100 363 -166 99 -266 1819 -15084 65 -1710 97 -1120 65 -932 231 -132 367 -166 623 -394 165 -66 231 -332 499 -15316 67 -166 99 -334 97 -728 465 -362 67 -234 65 -234 131 -364 761 -364 195 -66 723 -64 2249 -166 6311 -12186 99 -334 67 -100 131 -134 65 -758 97 -1020 65 -364 65 -460 129 -66 99 -200 233 -100 201 -164 431 -68 5209 -13362 99 -98 65 -820 65 -632 99 -1262 97 -198 163 -66 65 -724 163 -166 1225 -100 13917 -12122 199 -464 99 -164 99 -296 65 -724 65 -164 131 -196 131 -626 131 -332 331 -132 557 -232 131 -394 295 -228 1645 -98 14941 -12746 99 -300 99 -2656 693 -166 165 -66
|
||||
RAW_Data: 167 -68 429 -298 44699 -9866 65 -3424 131 -1426 163 -232 131 -368 2963 -12616 131 -430 197 -728 97 -132 129 -132 129 -424 97 -98 2131 -66 2093 -100 33049 -6140 99 -632 133 -66 99 -266 99 -1294 129 -654 97 -592 199 -296 327 -134 2123 -68 131 -66 3393 -12832 263 -1988 133 -100 65 -662 99 -298 199 -472 1359 -100 23585 -12526 65 -1222 65 -460 131 -560 131 -528 163 -394 131 -98 295 -98 65 -66 165 -98 361 -330 197 -162 459 -98 1125 -68 32731 -7332 383 -256 325 -292 291 -290 291 -288 291 -288 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -288 289 -288 309 -276 283 -304 319 -286 289 -290 291 -292 325 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -290 325 -254 323 -290 287 -290 285 -278 307 -284 303 -318 285 -290 287 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -288 289 -290 323 -272 275 -320 307 -284 287 -288 327 -252 327 -288 291 -288 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 291 -326 291 -290 291 -288 289 -288 289 -324 271 -276 319 -276 319 -288 287 -290 289 -328 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -290 289 -290 289 -290 323 -290 289 -288 289 -288 307 -276 283 -306 317 -288 289 -288 291 -326 291 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 287 -290 287 -278 305 -286 303 -318 287 -288 325 -254 327 -288 291 -288 291 -292 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 289 -288 289 -288 307 -276 317 -274 319 -288 289 -288 291 -326 291 -292 289 -292 289 -292 291 -290 327 -290 289 -290 289 -290 289 -290 327 -288 289 -288 289 -288 289 -288 307 -276 317 -274 319 -288 287 -290 291 -328 289 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -288 289 -288 287 -308 277 -286 303 -318 285 -288 327 -252 327 -288 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 287 -290 287 -288 307 -276 317 -274 319 -286 289 -290 291 -328 289 -292 289 -292 291 -290 291 -290 327 -290 289 -290 289 -290 291 -288 327 -288 289 -290 289 -290 287 -288 307 -276 285 -304 319 -286 289 -290 289 -328 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290
|
||||
RAW_Data: 291 -292 289 -292 289 -292 325 -290 291 -288 289 -288 323 -272 277 -616 579 -572 607 -564 617 -284 287 -326 291 -290 291 -572 293 -278 321 -276 589 -318 287 -290 291 -578 309 -286 591 -292 301 -314 285 -288 289 -290 291 -326 291 -292 289 -292 289 -292 291 -290 327 -538 309 -322 559 -296 301 -582 577 -316 285 -602 287 -282 597 -318 287 -292 289 -600 561 -580 593 -316 285 -288 327 -550 315 -286 289 -326 563 -290 283 -302 317 -286 287 -596 281 -326 289 -290 571 -296 277 -320 309 -550 611 -278 321 -288 289 -290 287 -594 587 -312 285 -594 575 -312 285 -290 287 -594 587 -580 611 -588 581 -586 309 -286 287 -290 289 -328 291 -290 583 -278 323 -564 289 -284 301 -316 287 -290 583 -590 587 -292 283 -304 317 -552 317 -286 325 -290 573 -310 271 -112012 99 -66 101 -464 263 -264 65 -328 65 -1916 131 -492 197 -168 365 -198 197 -66 99 -100 265 -66 99 -100 263 -66 1919 -15284 165 -896 165 -734 99 -498 99 -964 99 -530 133 -334 263 -132 265 -164 97 -100 19789 -12686 99 -460 101 -66 165 -66 429 -198 131 -296 295 -292 1511 -98 433 -11538 65 -630 133 -430 131 -198 99 -334 131 -2670 197 -132 131 -98 231 -130 1017 -232 1807 -66 42269 -12644 131 -2450 165 -134 99 -134 165 -232 67 -166 165 -164 99 -332 16211 -12510 65 -196 99 -264 297 -164 165 -168 99 -632 99 -334 165 -234 65 -268 395 -362 563 -132 5011 -12378 97 -396 99 -228 97 -100 229 -98 163 -1220 165 -198 65 -930 131 -198 533 -66 99 -166 165 -134 465 -132 4399 -14500 233 -1722 795 -132 67 -66 99 -98 43413 -12206 67 -828 165 -868 97 -700 65 -398 165 -368 65 -430 563 -200 231 -134 99 -166 231 -100 65 -100 131 -266 561 -100 855 -134 19743 -12590 163 -918 163 -1936 65 -804 165 -68 265 -100 197 -132 697 -66 1809 -13450 97 -132 65 -264 97 -326 97 -1028 97 -400 131 -460 131 -294 555 -296 65 -130 197 -66 35633 -12042 65 -134 131 -134 165 -200 67 -628 199 -1094 133 -1054 465 -202 231 -132 165 -168 43879 -12362 231 -294 197 -260 161 -98 231 -458 165 -134 135 -1164 165 -98 133 -166 131 -266 2123 -15156 99 -1828 133 -98 67 -198 67 -1290 363 -264 163 -264 723 -294 951 -15412 97 -886 129 -132 131 -396 97 -362 99 -892 65 -330 163 -100 131 -134 233 -66 701 -66 231 -298 16959 -7436 353 -290 291 -292 287 -290 287 -326 287 -290 287 -290 289 -324 289 -288 289 -288 287 -322 269 -278 319 -310 283 -286 287 -326 287 -290 287 -290 289 -290 291 -324 291 -288
|
||||
RAW_Data: 291 -288 289 -290 287 -326 287 -288 287 -324 269 -278 285 -306 315 -286 287 -326 287 -290 287 -290 289 -288 289 -324 289 -288 289 -290 287 -326 287 -290 287 -288 287 -306 277 -318 273 -318 285 -288 323 -288 291 -288 291 -290 289 -290 325 -290 287 -290 289 -288 289 -324 289 -288 289 -288 285 -306 277 -320 271 -318 285 -288 287 -326 287 -290 289 -288 289 -324 289 -288 289 -288 289 -326 287 -290 287 -288 287 -322 271 -278 317 -310 283 -286 287 -324 289 -290 289 -288 291 -288 291 -326 287 -290 287 -290 287 -326 289 -288 289 -288 287 -322 269 -278 287 -340 283 -286 287 -326 287 -290 287 -290 287 -290 287 -326 289 -288 289 -288 289 -324 289 -290 287 -288 285 -308 277 -318 273 -316 287 -286 325 -288 289 -288 291 -290 289 -326 287 -290 287 -290 289 -288 289 -324 289 -288 287 -288 277 -308 283 -304 317 -286 287 -326 251 -326 287 -290 287 -290 287 -326 289 -288 289 -288 289 -326 287 -288 289 -286 287 -306 277 -320 273 -318 285 -286 323 -288 291 -288 291 -288 289 -324 289 -288 289 -290 287 -326 251 -326 287 -288 287 -322 271 -278 285 -340 283 -286 285 -326 287 -290 287 -290 287 -326 253 -324 289 -288 289 -288 289 -326 287 -288 287 -286 277 -308 285 -304 317 -286 287 -324 289 -288 291 -288 291 -290 287 -326 287 -290 287 -290 287 -326 289 -288 289 -288 285 -322 269 -280 287 -340 283 -286 285 -326 287 -290 287 -290 287 -326 253 -324 289 -288 289 -288 289 -326 287 -288 287 -322 271 -278 285 -304 317 -286 287 -322 291 -288 291 -288 289 -288 287 -326 289 -288 289 -288 289 -324 287 -288 287 -288 275 -308 285 -304 315 -286 287 -324 289 -288 289 -288 289 -324 253 -324 289 -290 287 -290 287 -324 289 -288 285 -322 271 -278 285 -340 281 -286 287 -324 289 -288 291 -288 289 -324 287 -290 287 -290 287 -290 287 -326 287 -286 287 -322 269 -280 285 -340 283 -588 573 -590 579 -606 579 -314 285 -288 287 -324 287 -564 289 -322 273 -318 553 -316 285 -324 289 -570 307 -270 603 -312 283 -286 287 -326 287 -290 287 -290 289 -324 289 -288 289 -570 293 -310 589 -584 571 -616 583 -310 283 -590 573 -310 285 -322 239 -632 273 -316 583 -282 285 -322 289 -288 287 -324 287 -566 579 -606 279 -324 547 -620 581 -292 295 -312 285 -588 573 -300 277 -604 309 -284 573 -588 291 -322 273 -318 283 -288 583 -314 285 -322 287 -564 291 -286 301 -316 587 -572 605 -290 295 -310 281 -578 589 -580 601 -584 315 -284 287 -288
|
||||
RAW_Data: 289 -324 287 -288 285 -308 565 -606 279 -324 547 -312 285 -324 287 -288 285 -322 269 -602 273 -318 285 -286 585 -314 285 -110226 197 -132 99 -21542 65 -462 65 -528 65 -658 65 -130 65 -330 63 -164 97 -1116 129 -164 195 -460 363 -298 99 -166 21279 -6344 65 -624 131 -132 227 -1530 99 -432 1461 -132 299 -66 6201 -17182 365 -200 199 -266 65 -98 429 -134 493 -17436 265 -694 131 -1028 691 -66 197 -132 559 -230 4967 -12040 331 -134 99 -300 67 -132 197 -796 131 -464 427 -460 495 -100 623 -132 65 -330 163 -66 161 -64 789 -12302 163 -994 65 -334 67 -534 97 -630 197 -662 67 -130 131 -100 689 -166 65 -68 197 -134 6467 -15030 97 -132 99 -600 99 -298 97 -134 133 -66 133 -102 65 -132 199 -68 631 -66 265 -98 165 -68 265 -12048 97 -234 231 -66 163 -792 199 -1630 65 -366 99 -330 265 -198 65 -230 561 -200 165 -230 1227 -66 15501 -12162 165 -198 131 -200 65 -1688 133 -200 67 -100 165 -500 131 -100 829 -100 997 -132 2431 -12842 65 -724 131 -298 99 -392 131 -664 131 -656 197 -64 65 -428 199 -66 199 -166 5953 -10478 65 -1496 99 -100 165 -566 131 -864 97 -328 65 -690 67 -1224 165 -68 595 -198 265 -98 165 -100 363 -66 3673 -12608 65 -528 97 -1848 67 -332 99 -368 133 -234 989 -68 233 -166 263 -98 71113 -12854 99 -464 233 -198 167 -366 99 -66 2523 -98 15297 -16788 199 -464 65 -266 165 -200 1897 -98 54609 -12790 131 -1134 363 -566 65 -200 131 -866 331 -130 361 -66 429 -66 131 -362 261 -132 2143 -66 2201 -16678 197 -762 165 -262 293 -262 97 -230 461 -98 623 -64 6725 -12216 99 -990 97 -428 99 -364 131 -462 67 -696 67 -830 697 -98 229 -66 263 -98 99 -296 195 -100 957 -66 2227 -12318 131 -2888 131 -1030 99 -132 199 -234 163 -166 593 -296 1677 -64 3541 -11436 131 -890 265 -366 397 -268 331 -730 97 -1066 65 -134 65 -166 297 -264 65 -164 197 -66 425 -394 793 -15064 65 -528 67 -300 165 -132 67 -332 65 -462 197 -1922 133 -100 165 -200 165 -68 197 -134 65 -132 229 -264 265 -198 1625 -134 301 -100 20661 -12034 97 -166 65 -432 197 -730 163 -524 63 -100 97 -230 131 -198 97 -196 459 -66 227 -66 131 -66 65 -164 99 -162 131 -98 491 -66 10029 -12164 131 -1054 197 -890 197 -460 131 -1192 665 -100 165 -168 729 -68 197 -134 99 -134 827 -166 265 -68 2285 -11580 65 -602 131 -134 65 -132 65 -66 263 -324 229 -990 163 -692 195 -460 297 -132 531 -132 233 -98 37651 -310 283 -322 247 -322 285 -322
|
||||
RAW_Data: 247 -322 283 -320 239 -310 287 -304 315 -284 285 -324 287 -288 287 -288 289 -326 287 -290 287 -290 287 -326 289 -288 289 -286 287 -308 275 -288 303 -316 287 -286 289 -326 291 -288 291 -288 291 -290 289 -326 287 -290 287 -290 289 -324 289 -288 289 -288 287 -324 269 -278 285 -306 317 -286 287 -326 287 -292 289 -290 289 -288 289 -324 289 -290 287 -290 287 -326 287 -290 287 -288 287 -308 275 -286 303 -318 285 -288 287 -326 291 -288 291 -288 289 -288 289 -326 287 -290 287 -290 287 -326 289 -288 289 -288 287 -322 271 -278 319 -310 283 -288 287 -324 289 -288 289 -288 289 -290 287 -326 287 -290 287 -290 287 -326 289 -288 285 -288 305 -278 285 -302 317 -286 287 -324 291 -288 289 -288 289 -288 289 -326 287 -290 287 -290 287 -326 289 -288 289 -288 287 -322 271 -278 319 -308 283 -288 287 -324 289 -288 289 -290 287 -290 287 -326 287 -290 287 -290 289 -324 289 -288 285 -286 307 -278 287 -304 315 -286 287 -290 287 -326 287 -290 289 -288 289 -324 289 -288 289 -290 287 -326 287 -290 287 -288 287 -306 277 -318 273 -318 283 -288 289 -324 289 -290 287 -290 287 -326 287 -290 287 -290 289 -324 289 -288 287 -288 285 -308 277 -286 303 -316 285 -288 289 -324 289 -288 289 -288 289 -326 287 -290 287 -290 287 -326 251 -326 287 -290 285 -324 271 -278 285 -304 317 -286 287 -324 289 -288 289 -288 289 -324 253 -326 287 -290 287 -290 287 -326 287 -288 287 -276 309 -284 301 -314 287 -286 287 -324 291 -288 289 -288 289 -326 287 -290 287 -290 287 -326 251 -326 287 -288 287 -324 269 -278 285 -340 283 -286 285 -326 289 -288 289 -288 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 325 -254 323 -290 287 -290 285 -278 305 -286 303 -318 287 -290 287 -292 325 -292 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -288 289 -290 287 -308 275 -284 305 -320 287 -288 291 -290 325 -290 289 -290 289 -290 327 -252 327 -292 289 -292 289 -292 291 -290 291 -602 577 -602 581 -574 587 -292 319 -274 317 -288 289 -596 281 -288 327 -292 573 -310 269 -278 321 -578 317 -288 581 -278 323 -288 289 -288 323 -272 277 -286 339 -284 287 -290 287 -598 571 -588 615 -576 319 -288 547 -622 581 -584 579 -318 287 -580 587 -292 283 -304 317 -558 317 -288 291 -290 327 -292 289 -292 571 -294 279 -618 581 -282 323 -292 291 -290 291 -290 291 -292 289 -292 327 -290 289 -290 289 -288
|
||||
RAW_Data: 289 -592 293 -300 579 -320 287 -292 291 -600 277 -288 325 -292 289 -290 289 -290 287 -290 307 -276 317 -272 319 -288 583 -282 325 -290 289 -292 291 -290 291 -568 319 -280 597 -318 287 -292 289 -292 291 -602 275 -324 563 -580 603 -284 323 -292 289 -292 291 -564 313 -288 289 -328 563 -290 283 -102430 395 -196 359 -64 99 -562 165 -528 163 -888 131 -1298 131 -892 463 -132 167 -166 165 -134 131 -100 65 -66 299 -68 795 -12114 65 -862 131 -66 97 -898 163 -396 131 -1022 63 -364 627 -200 233 -100 763 -198 891 -15144 99 -398 97 -756 65 -626 133 -732 97 -266 101 -100 133 -264 131 -100 233 -100 165 -66 99 -630 133 -132 1161 -66 465 -134 9423 -12460 97 -724 65 -2146 97 -830 131 -100 265 -266 133 -98 197 -12070 131 -786 65 -726 163 -562 99 -300 65 -1054 163 -132 329 -166 363 -166 659 -12556 199 -1332 131 -366 131 -166 67 -364 65 -334 231 -168 231 -296 231 -132 425 -66 557 -98 1187 -15158 165 -264 65 -100 99 -1190 65 -230 63 -1054 131 -132 731 -528 163 -66 163 -66 755 -16918 329 -298 331 -66 97 -98 197 -64 297 -198 11551 -12470 65 -398 67 -234 99 -200 99 -996 165 -66 67 -1196 99 -334 993 -200 267 -98 331 -68 12155 -12698 163 -68 229 -2676 65 -530 231 -464 231 -98 131 -300 1061 -15398 329 -200 65 -468 299 -532 131 -2052 265 -298 133 -134 65 -66 99 -200 231 -364 65 -66 595 -66 499 -132 565 -166 3189 -11422 65 -1028 97 -1190 165 -600 63 -330 197 -68 131 -1100 199 -66 133 -68 363 -132 755 -132 2085 -66 44597 -12106 131 -264 133 -962 65 -664 65 -364 301 -134 167 -298 99 -98 233 -66 65 -134 22489 -13898 67 -1558 233 -298 99 -266 199 -100 165 -66 431 -98 165 -98 165 -100 41139 -12224 131 -430 299 -232 99 -230 131 -294 129 -266 99 -328 165 -198 65 -100 97 -690 99 -130 921 -164 623 -64 1253 -12184 199 -264 65 -394 131 -328 131 -628 133 -364 97 -196 197 -98 393 -66 459 -198 429 -266 2417 -11796 99 -536 97 -600 67 -1222 131 -890 65 -100 165 -168 133 -132 131 -130 561 -66 201 -100 65 -12342 133 -1160 99 -132 67 -232 65 -1326 99 -200 199 -434 165 -66 299 -66 165 -66 167 -234 133 -132 331 -166 99 -196 1291 -100 165 -166 25489 -12074 129 -688 131 -998 165 -334 165 -694 165 -562 65 -558 231 -132 131 -66 131 -164 163 -98 131 -294 99 -132 1055 -12068 131 -964 99 -528 361 -66 131 -130 727 -66 133 -132 99 -166 597 -288 325 -290 289 -290 291 -288 291 -292 325 -254 323 -290
|
||||
RAW_Data: 287 -290 287 -290 307 -276 317 -274 317 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -290 327 -292 289 -292 289 -290 289 -288 307 -278 317 -272 317 -286 289 -290 327 -252 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -290 289 -292 287 -290 287 -290 307 -276 317 -274 319 -288 287 -290 289 -328 289 -292 291 -290 291 -288 327 -252 327 -290 289 -290 289 -290 291 -288 325 -288 289 -288 289 -290 323 -270 277 -318 275 -320 285 -290 289 -290 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 287 -290 287 -278 307 -284 301 -316 287 -288 325 -254 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -288 289 -290 287 -308 275 -318 273 -320 287 -288 289 -290 327 -292 289 -292 289 -292 291 -288 291 -290 327 -290 289 -290 289 -290 291 -290 327 -292 289 -292 287 -290 287 -290 307 -276 283 -304 319 -288 287 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -288 291 -288 289 -288 323 -272 277 -286 305 -318 287 -288 289 -290 327 -290 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 289 -290 287 -290 287 -324 271 -278 319 -274 319 -286 289 -288 327 -292 289 -290 289 -290 291 -288 327 -288 291 -290 289 -290 289 -292 289 -292 327 -292 289 -288 289 -288 289 -324 271 -276 319 -274 319 -288 287 -292 291 -326 291 -292 289 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 327 -252 327 -288 289 -288 289 -288 307 -276 317 -272 319 -288 289 -290 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -290 287 -290 287 -308 275 -318 273 -320 287 -288 291 -584 601 -582 585 -582 611 -280 321 -290 291 -290 289 -594 291 -292 309 -286 587 -284 287 -292 327 -568 309 -288 561 -292 321 -274 319 -288 289 -290 291 -290 327 -292 289 -292 289 -566 597 -294 309 -590 581 -572 617 -582 275 -320 585 -580 309 -288 287 -290 593 -586 313 -286 287 -288 327 -254 597 -318 287 -574 579 -294 301 -316 287 -288 289 -596 283 -274 309 -278 617 -284 323 -290 289 -290 289 -578 587 -582 305 -318 553 -318 287 -580
|
||||
RAW_Data: 309 -288 595 -292 293 -580 579 -590 615 -582 577 -318 289 -290 291 -580 311 -288 595 -582 311 -286 583 -574 587 -290 321 -272 317 -286 291 -288 291 -598 571 -310 287 -324 271 -276 319 -576 317 -288 291 -290 579 -312 287 -106602 327 -264 429 -988 99 -332 63 -1252 131 -1592 199 -334 365 -236 65 -132 529 -432 21543 -5368 99 -6920 163 -1976 65 -660 97 -628 99 -98 99 -200 133 -234 133 -100 333 -132 495 -166 133 -334 995 -132 165 -98 299 -100 199 -98 52745 -12184 97 -434 163 -666 265 -430 67 -1624 297 -100 165 -98 267 -100 331 -66 561 -66 2057 -100 625 -12150 231 -266 131 -230 99 -262 165 -264 165 -1318 97 -132 295 -132 97 -232 235 -200 495 -166 131 -100 13527 -12126 165 -558 99 -164 99 -330 99 -200 167 -1362 199 -500 165 -266 65 -166 433 -170 1381 -166 45963 -10220 99 -690 165 -764 131 -400 99 -100 99 -1762 165 -100 233 -368 199 -68 97 -66 431 -198 131 -166 1417 -12314 229 -360 363 -68 65 -298 67 -696 199 -896 133 -298 99 -100 231 -364 165 -166 499 -66 3557 -12344 163 -730 65 -696 133 -66 99 -2286 229 -100 695 -100 231 -200 133 -162 129 -100 2071 -198 16359 -12162 97 -724 65 -2876 99 -400 65 -528 131 -560 197 -98 131 -66 129 -98 689 -98 14541 -12208 297 -462 99 -366 99 -132 99 -332 99 -1722 231 -432 99 -66 233 -134 133 -232 165 -166 561 -66 131 -134 2485 -13126 131 -3078 99 -66 829 -132 465 -264 165 -200 1993 -100 1825 -12178 131 -628 133 -370 133 -1088 361 -296 493 -64 131 -164 261 -132 131 -66 231 -66 4509 -13314 65 -66 131 -1546 97 -232 231 -632 265 -266 165 -100 467 -166 297 -200 295 -132 261 -66 22643 -12058 97 -656 129 -854 131 -198 195 -758 97 -660 97 -132 631 -66 299 -66 527 -66 131 -100 1527 -134 563 -10768 65 -1778 65 -198 97 -66 99 -790 131 -826 65 -432 133 -992 99 -602 167 -166 363 -66 231 -628 699 -66 15931 -12212 97 -370 165 -298 195 -296 265 -132 233 -132 529 -132 133 -100 265 -100 131 -300 7849 -248 325 -288 287 -288 285 -308 277 -284 303 -316 285 -288 287 -326 287 -290 287 -290 289 -324 289 -288 289 -288 289 -326 287 -290 287 -288 287 -322 271 -278 285 -340 283 -286 287 -326 289 -288 289 -288 289 -288 325 -288 289 -288 289 -288 325 -288 287 -288 285 -308 277 -284 301 -316 285 -288 287 -326 289 -288 289 -288 289 -324 289 -290 287 -290 287 -326 251 -326 287 -288 287 -324 269 -278 285 -340 283 -286 287 -324 289 -288 289 -288 289 -326 287 -290 287 -290
|
||||
RAW_Data: 287 -290 323 -288 289 -286 285 -308 279 -284 301 -316 285 -288 289 -324 289 -288 289 -288 289 -326 287 -290 287 -290 287 -326 251 -326 289 -288 285 -324 269 -278 285 -342 281 -286 287 -324 289 -290 287 -290 287 -326 251 -326 289 -288 289 -288 323 -288 287 -286 285 -308 277 -318 271 -316 285 -286 289 -324 289 -288 289 -288 289 -326 287 -290 287 -290 287 -326 287 -290 287 -288 285 -324 269 -280 287 -340 283 -286 287 -324 289 -290 287 -290 287 -326 251 -326 287 -290 289 -288 289 -324 287 -286 287 -308 277 -284 301 -316 285 -286 289 -324 289 -288 289 -290 287 -326 287 -290 287 -290 289 -324 253 -324 287 -288 287 -322 269 -278 289 -340 283 -286 287 -324 289 -288 289 -288 289 -326 251 -326 287 -290 287 -290 287 -326 287 -288 285 -308 277 -286 299 -316 285 -286 289 -324 289 -288 289 -288 289 -326 287 -290 287 -290 287 -326 253 -324 289 -286 287 -322 269 -278 287 -342 281 -286 287 -326 287 -290 287 -290 289 -324 253 -324 289 -288 289 -288 289 -324 287 -288 285 -308 277 -284 301 -316 285 -286 289 -324 289 -288 289 -288 289 -326 287 -290 287 -290 287 -326 287 -288 287 -288 287 -306 277 -318 273 -318 285 -286 287 -326 289 -288 289 -288 289 -324 289 -288 289 -290 287 -326 287 -288 287 -288 285 -306 279 -320 271 -316 287 -286 287 -324 289 -288 289 -290 287 -326 287 -290 287 -290 289 -324 289 -288 289 -288 285 -322 271 -278 287 -342 281 -286 287 -326 287 -290 287 -290 289 -324 253 -324 289 -288 289 -288 289 -326 287 -288 287 -286 305 -278 319 -272 317 -588 577 -590 579 -604 579 -314 287 -288 287 -326 251 -600 289 -286 303 -314 583 -284 285 -326 287 -572 293 -278 619 -280 287 -324 289 -288 289 -288 289 -324 289 -290 287 -290 287 -600 577 -606 577 -600 287 -278 285 -342 283 -286 583 -316 285 -288 287 -326 251 -602 289 -288 599 -318 285 -596 277 -286 601 -580 301 -312 553 -608 283 -280 311 -296 311 -284 575 -300 277 -606 311 -284 281 -310 281 -310 585 -282 285 -596 599 -308 271 -310 291 -310 283 -286 287 -326 287 -560 601 -310 269 -602 275 -318 285 -288 289 -324 289 -288 289 -288 289 -326 287 -290 287 -290 567 -294 311 -298 313 -284 285 -594 573 -308 285 -592 587 -578 317 -286 575 -310 283 -322 271 -600 275 -318 285 -286 583 -316 283 -110508 65 -332 197 -466 99 -1054 97 -1262 231 -234 463 -100 499 -66 335 -100 165 -166 6723 -12444 131 -494 395 -166 165 -230 129 -1548 759 -166 131 -66
|
||||
RAW_Data: 263 -132 457 -66 163 -166 917 -66 76631 -12462 163 -692 131 -2548 195 -66 131 -132 261 -64 229 -228 15221 -13728 65 -398 97 -430 65 -1590 65 -398 165 -100 367 -130 99 -132 65 -234 163 -132 661 -166 97 -166 619 -98 31547 -1450 131 -366 135 -494 99 -2258 99 -200 235 -68 131 -332 235 -166 365 -398 329 -132 99 -1864 263 -132 131 -102 401 -66 467 -264 1059 -132 299 -198 97 -198 833 -166 391 -66 589 -164 3057 -6278 167 -2150 97 -234 165 -502 133 -296 365 -398 131 -368 99 -232 233 -130 97 -164 457 -100 621 -64 855 -164 853 -130 589 -98 22907 -302 203 -398 133 -164 227 -464 295 -228 165 -492 131 -100 99 -2052 65 -524 65 -430 233 -168 265 -166 99 -268 165 -362 65 -202 499 -198 969 -68 1167 -102 367 -100 337 -166 7725 -3078 65 -398 97 -132 63 -494 163 -100 97 -558 165 -64 65 -162 131 -496 297 -700 131 -466 131 -430 131 -232 589 -66 2197 -130 161 -362 65 -394 165 -64 295 -132 65 -3994 299 -134 65 -1030 263 -164 99 -2804 261 -198 355 -394 297 -170 167 -892 1425 -166 1101 -132 499 -66 199 -166 231 -102 1429 -134 67707 -12136 67 -1452 65 -728 65 -300 131 -930 67 -530 199 -166 399 -66 563 -232 1295 -12220 165 -3602 65 -622 559 -164 165 -334 131 -66 167 -532 231 -198 1295 -13678 131 -360 397 -432 331 -134 131 -266 97 -300 599 -66 99 -232 959 -130 33201 -9822 65 -2518 99 -292 99 -630 65 -996 65 -732 331 -100 397 -200 165 -168 65 -436 197 -66 199 -100 131 -200 829 -100 11279 -10450 65 -2048 65 -328 63 -296 65 -1526 131 -926 65 -100 99 -398 67 -166 65 -530 761 -266 163 -100 2359 -16006 429 -1120 99 -730 67 -864 531 -66 461 -66 763 -264 1095 -66 425 -98 17457 -13200 97 -230 227 -690 199 -992 229 -130 97 -426 229 -330 195 -330 821 -98 163 -100 26165 -12730 97 -428 129 -330 99 -556 131 -928 431 -166 261 -100 395 -198 131 -236 929 -66 663 -15178 67 -764 393 -274 281 -322 279 -276 313 -280 285 -340 283 -282 275 -312 279 -320 273 -318 281 -278 311 -280 285 -342 281 -282 275 -314 279 -320 271 -320 279 -278 313 -278 287 -342 281 -282 275 -314 277 -322 307 -284 279 -278 311 -280 321 -272 319 -280 277 -314 277 -318 273 -318 281 -276 313 -280 285 -342 281 -282 275 -314 279 -318 273 -320 279 -276 313 -280 319 -308 283 -280 277 -314 279 -318 273 -318 281 -276 313 -280 317 -308 283 -284 275 -314 279 -320 271 -318 279 -278 311 -280 287 -306 317 -282 277 -314 277 -320 271 -320 279 -276
|
||||
RAW_Data: 313 -280 285 -342 281 -282 275 -314 279 -320 273 -318 281 -276 313 -280 285 -340 283 -282 275 -314 279 -318 273 -318 281 -278 311 -278 319 -308 285 -282 275 -314 279 -320 273 -316 281 -276 313 -280 285 -340 283 -282 277 -312 279 -320 271 -318 281 -278 313 -278 287 -340 283 -280 277 -314 277 -320 273 -318 281 -278 311 -280 285 -340 283 -282 275 -314 279 -318 273 -318 281 -278 311 -280 319 -274 317 -284 275 -314 277 -320 271 -318 281 -276 313 -280 285 -342 281 -282 277 -312 279 -320 271 -320 279 -278 311 -280 287 -340 283 -282 275 -314 279 -318 273 -318 281 -278 311 -280 285 -340 283 -280 277 -314 279 -320 273 -318 279 -280 311 -280 319 -274 317 -282 275 -314 279 -318 273 -318 279 -278 313 -280 285 -340 281 -280 277 -314 279 -320 271 -318 281 -278 313 -278 287 -340 283 -280 277 -314 277 -320 273 -318 281 -278 311 -280 285 -342 281 -282 277 -312 279 -320 309 -282 279 -278 311 -280 321 -274 317 -282 275 -314 277 -318 273 -318 281 -276 313 -280 285 -342 281 -282 275 -314 279 -318 273 -320 279 -278 313 -278 287 -340 283 -282 277 -312 279 -320 271 -318 281 -278 311 -280 319 -308 283 -282 275 -314 279 -320 271 -318 279 -278 311 -280 287 -306 317 -282 277 -312 279 -320 271 -320 281 -276 313 -278 287 -340 283 -280 277 -314 277 -320 273 -318 279 -278 313 -280 285 -340 283 -282 275 -314 279 -320 271 -320 279 -278 313 -280 317 -274 319 -280 277 -314 279 -320 271 -316 281 -276 313 -280 287 -606 577 -596 575 -628 581 -282 277 -310 279 -322 273 -618 283 -278 309 -280 579 -314 281 -310 279 -604 309 -284 575 -302 275 -312 295 -312 283 -282 275 -314 279 -322 275 -318 279 -604 573 -302 277 -320 285 -592 573 -308 285 -322 271 -276 287 -342 281 -590 281 -322 289 -288 571 -308 271 -602 577 -586 313 -286 287 -324 287 -290 595 -290 293 -576 577 -314 285 -324 287 -564 289 -288 303 -316 583 -282 283 -324 289 -570 579 -592 613 -588 311 -284 563 -616 571 -586 313 -288 567 -294 309 -594 577 -316 285 -288 289 -324 287 -564 579 -606 585 -316 285 -288 289 -324 253 -324 289 -288 285 -324 269 -600 575 -316 287 -288 289 -288 289 -324 289 -572 307 -272 279 -324 575 -318 285 -111386 133 -132 67 -632 131 -300 131 -862 133 -232 99 -366 265 -100 531 -168 265 -234 3317 -13002 99 -300 97 -2802 67 -166 397 -66 561 -230 229 -66 199 -168 4557 -15414 133 -530 131 -130 261 -164 131 -230 165 -132 631 -100 897 -66
|
||||
RAW_Data: 25281 -6356 331 -302 165 -234 67 -5290 131 -134 99 -1716 131 -130 193 -394 393 -66 163 -98 1935 -66 793 -100 120481 -12442 131 -1422 65 -726 65 -464 65 -1518 263 -98 97 -328 197 -660 99 -132 1369 -66 695 -13184 97 -392 1349 -98 65 -296 295 -66 955 -132 4985 -12550 197 -100 97 -362 163 -2080 429 -164 493 -98 131 -230 1153 -66 2787 -12078 163 -922 131 -2626 131 -368 463 -498 265 -166 167 -68 165 -234 167 -66 11627 -11298 99 -1122 195 -962 65 -200 267 -1558 97 -166 865 -164 433 -12334 165 -994 131 -1250 99 -298 65 -268 99 -764 795 -66 201 -100 231 -200 167 -134 67 -100 457 -98 46313 -13156 99 -196 97 -986 129 -854 131 -66 97 -166 393 -98 297 -98 891 -132 231 -66 30329 -11894 97 -722 231 -66 133 -1262 199 -134 465 -168 731 -66 197 -168 1557 -66 431 -66 7775 -13536 65 -900 99 -468 333 -266 563 -264 99 -66 431 -100 99 -232 24403 -13834 131 -590 63 -1614 131 -526 231 -198 331 -198 199 -262 11191 -5298 99 -7224 65 -760 131 -296 129 -164 65 -132 131 -924 133 -962 267 -100 99 -100 757 -98 129 -66 131 -98 229 -64 22105 -316 287 -286 287 -326 289 -288 289 -288 289 -324 289 -290 287 -290 287 -290 287 -326 287 -288 287 -324 269 -278 317 -274 315 -286 287 -322 291 -288 291 -288 289 -290 287 -326 287 -290 289 -288 289 -324 289 -288 287 -288 287 -306 277 -318 273 -318 285 -288 287 -326 287 -290 287 -290 289 -324 289 -288 289 -288 289 -326 287 -290 287 -288 287 -322 271 -278 319 -274 317 -284 287 -324 291 -288 291 -288 289 -288 289 -324 289 -288 289 -290 287 -326 287 -288 287 -288 285 -306 277 -320 273 -318 285 -288 287 -326 287 -290 287 -290 287 -326 289 -288 289 -288 289 -324 287 -290 287 -286 287 -306 277 -320 271 -316 287 -286 323 -288 289 -288 287 -290 289 -324 289 -288 289 -288 289 -324 289 -288 287 -286 287 -306 277 -320 273 -318 283 -288 289 -324 289 -290 287 -290 287 -326 287 -290 289 -288 289 -324 287 -290 287 -288 285 -306 277 -320 271 -316 287 -286 325 -288 289 -290 289 -288 287 -324 289 -288 289 -288 289 -324 287 -290 285 -288 285 -308 277 -318 273 -318 283 -288 287 -326 289 -288 289 -288 289 -324 289 -288 289 -290 287 -324 287 -288 287 -286 287 -306 277 -320 273 -318 287 -286 323 -288 289 -288 287 -288 289 -326 287 -290 287 -290 287 -326 287 -288 287 -286 275 -308 285 -304 317 -286 285 -326 287 -290 287 -290 287 -326 251 -326 289 -288 289 -288 289 -324 289 -288 287 -286
|
||||
RAW_Data: 275 -310 285 -304 315 -286 287 -322 291 -288 289 -288 287 -324 289 -288 287 -290 289 -324 251 -324 287 -288 285 -322 271 -278 287 -342 283 -286 287 -324 289 -288 289 -288 289 -326 251 -326 287 -290 287 -290 287 -324 287 -288 285 -322 271 -278 287 -340 283 -286 285 -324 289 -288 289 -288 287 -324 289 -288 289 -288 287 -324 287 -288 287 -286 287 -306 277 -320 273 -318 283 -288 287 -326 289 -288 289 -288 289 -324 289 -288 289 -290 287 -326 287 -290 287 -288 285 -306 277 -320 271 -318 285 -288 287 -324 289 -288 287 -288 289 -324 289 -288 289 -288 287 -324 287 -288 287 -286 307 -276 287 -304 317 -286 285 -324 289 -288 289 -290 287 -326 251 -600 589 -580 603 -584 569 -306 307 -278 285 -340 281 -590 281 -322 287 -288 571 -294 279 -322 307 -586 283 -286 577 -308 285 -306 277 -320 271 -318 285 -288 287 -326 287 -290 287 -580 309 -286 591 -292 295 -312 283 -288 287 -594 279 -322 287 -290 287 -326 287 -290 563 -614 273 -316 285 -286 287 -326 547 -312 285 -600 579 -304 277 -320 285 -592 281 -324 571 -308 271 -278 323 -576 577 -622 581 -586 575 -584 599 -308 271 -312 291 -578 577 -312 285 -322 289 -564 291 -322 273 -318 285 -288 289 -324 547 -622 581 -584 575 -318 285 -598 557 -294 309 -596 577 -316 283 -602 579 -300 275 -318 285 -590 573 -310 285 -324 269 -278 319 -574 317 -286 287 -290 577 -308 283 -109914 131 -558 131 -98 65 -364 133 -98 99 -66 199 -330 3879 -10414 65 -2522 167 -1416 99 -294 229 -762 525 -196 295 -166 197 -132 2351 -66 11761 -12336 99 -1320 133 -760 67 -332 167 -530 265 -134 131 -268 791 -328 131 -230 231 -12694 99 -232 99 -326 99 -790 297 -66 229 -98 99 -818 65 -262 131 -260 329 -68 65 -166 361 -230 295 -66 457 -100 227 -130 525 -64 12531 -11426 97 -1124 99 -266 99 -66 133 -98 65 -300 65 -1062 199 -934 131 -630 233 -66 99 -564 233 -66 167 -68 199 -234 1611 -166 1099 -14108 99 -326 65 -1022 65 -360 65 -98 65 -198 1217 -10182 97 -1520 197 -992 461 -166 167 -200 167 -432 327 -100 227 -14828 65 -262 393 -132 199 -132 233 -66 99 -266 43347 -12178 65 -132 99 -132 131 -532 99 -360 131 -760 67 -1126 165 -732 199 -132 361 -296 97 -100 99 -98 197 -132 34409 -12382 131 -200 133 -470 131 -166 65 -1160 131 -364 99 -66 165 -266 565 -68 365 -166 165 -132 793 -132 5839 -11234 199 -200 131 -1030 65 -858 65 -856 65 -826 231 -132 199 -200 665 -100 165 -264 265 -66
|
||||
RAW_Data: 74255 -4890 329 -336 429 -9178 163 -132 97 -98 495 -100 133 -166 599 -268 231 -200 1889 -164 163151 -12432 131 -164 229 -262 97 -866 65 -460 263 -1058 199 -68 167 -134 97 -168 763 -66 165 -66 199 -100 2095 -13134 65 -726 65 -1196 99 -856 131 -328 129 -492 263 -98 133 -198 427 -64 359 -362 6657 -12100 199 -262 131 -134 65 -260 197 -132 165 -964 131 -786 97 -756 751 -66 957 -100 329 -66 133 -332 7517 -266 305 -294 311 -286 279 -276 311 -278 319 -276 321 -280 307 -282 277 -322 309 -284 279 -278 309 -280 321 -274 319 -282 275 -312 277 -318 275 -320 279 -276 311 -278 317 -276 319 -280 275 -312 277 -320 311 -286 279 -278 311 -278 317 -276 319 -280 275 -312 277 -320 309 -286 281 -276 311 -278 319 -274 321 -282 273 -314 277 -320 311 -284 279 -276 311 -280 319 -274 319 -280 275 -312 277 -320 273 -320 279 -278 311 -278 319 -276 319 -280 277 -312 277 -318 275 -320 279 -276 311 -280 319 -274 321 -280 275 -312 277 -320 273 -322 279 -278 311 -278 319 -274 319 -282 305 -282 277 -322 309 -284 279 -276 311 -280 319 -274 319 -282 275 -312 277 -318 275 -320 279 -276 311 -278 319 -274 321 -280 277 -312 277 -320 273 -320 279 -278 311 -278 319 -274 319 -282 275 -312 277 -320 309 -284 281 -276 311 -280 319 -276 321 -282 273 -314 277 -286 307 -318 279 -276 311 -278 319 -274 319 -282 275 -312 277 -320 309 -286 279 -278 311 -278 317 -276 319 -282 275 -312 277 -320 275 -320 279 -276 311 -278 319 -276 319 -280 275 -312 279 -318 309 -286 277 -278 311 -280 321 -274 321 -280 275 -312 279 -318 275 -320 279 -276 311 -278 317 -274 319 -282 275 -312 277 -320 275 -320 281 -276 311 -278 319 -274 319 -280 277 -312 277 -320 309 -286 279 -278 311 -278 317 -274 319 -280 277 -312 277 -322 309 -286 277 -278 311 -280 319 -274 321 -280 275 -314 277 -318 273 -320 279 -278 311 -278 317 -274 319 -282 275 -312 277 -320 309 -286 281 -276 311 -278 319 -274 319 -282 275 -312 277 -320 273 -320 281 -276 311 -278 319 -276 319 -282 273 -314 277 -322 309 -284 277 -278 309 -280 321 -274 321 -280 275 -312 277 -318 273 -322 279 -278 311 -278 319 -274 319 -280 277 -312 277 -318 311 -286 279 -276 311 -278 319 -274 321 -280 277 -312 277 -318 309 -286 279 -276 311 -278 319 -276 319 -282 305 -282 279 -320 307 -284 279 -278 309 -280 319 -276 319 -280 277 -312 277 -318 309 -284 281 -276 311 -278 319 -276 319 -282 275 -312 277 -320
|
||||
RAW_Data: 275 -320 281 -274 311 -600 575 -580 589 -584 615 -278 319 -286 291 -288 327 -546 315 -288 289 -290 603 -258 311 -298 315 -558 317 -288 579 -312 287 -288 325 -272 275 -318 273 -320 287 -290 289 -292 327 -290 291 -562 601 -274 307 -598 275 -322 585 -284 323 -540 617 -586 275 -320 287 -290 585 -572 617 -584 309 -286 585 -286 287 -578 617 -290 293 -578 319 -286 291 -288 291 -288 577 -618 581 -310 285 -290 289 -292 289 -292 327 -290 291 -292 289 -578 311 -288 287 -290 593 -294 297 -278 321 -288 289 -292 289 -588 317 -288 571 -580 327 -272 319 -286 287 -288 587 -572 615 -584 311 -286 287 -290 585 -572 615 -292 293 -278 321 -558 611 -276 323 -290 287 -290 321 -562 293 -298 313 -286 587 -284 287 -109408 333 -134 67 -266 265 -132 265 -234 65 -64 163 -100 1121 -66 131 -66 863 -166 3815 -12136 65 -432 97 -1298 131 -596 133 -1196 65 -266 199 -264 165 -164 165 -334 199 -98 1461 -68 3943 -12002 131 -364 165 -2108 261 -590 97 -524 229 -98 131 -130 65 -164 327 -166 133 -134 3543 -66 8173 -13214 163 -98 129 -658 97 -590 131 -300 65 -432 431 -100 129 -262 229 -164 99 -166 97 -592 35333 -10756 65 -1590 99 -234 301 -564 133 -2088 165 -66 99 -66 431 -66 267 -264 265 -132 131 -298 1823 -66 67 -100 3693 -12758 133 -1644 297 -858 129 -692 855 -134 293 -98 97 -262 329 -198 1507 -9354 131 -730 199 -1286 131 -296 131 -658 295 -230 625 -100 259 -64 883 -98 3533 -16698 495 -66 761 -132 1695 -66 44057 -12486 65 -98 97 -2542 97 -1030 165 -132 195 -132 659 -132 423 -196 1559 -100 14905 -16318 99 -498 229 -334 165 -200 197 -166 131 -400 17579 -13386 199 -98 65 -326 97 -792 65 -624 231 -132 97 -262 163 -166 131 -262 527 -132 721 -98 565 -98 629 -15118 65 -532 131 -964 131 -1132 165 -200 99 -398 727 -68 395 -166 461 -100 997 -132 1297 -15300 65 -426 65 -654 99 -198 329 -430 97 -954 787 -132 131 -132 97 -66 41739 -11716 101 -364 99 -526 199 -100 101 -1822 493 -100 995 -98 97 -66 1279 -11010 65 -1082 195 -732 99 -398 133 -1396 131 -1194 233 -102 135 -168 165 -100 299 -68 97 -100 265 -528 2221 -12260 99 -562 99 -662 67 -98 165 -834 99 -200 199 -398 429 -166 167 -332 99 -166 999 -100 14767 -10574 97 -1482 65 -434 165 -526 65 -234 131 -100 199 -666 425 -266 131 -298 801 -134 397 -66 197 -100 165 -134 6313 -13600 67 -698 99 -698 99 -296 297 -232 265 -134 199 -264 133 -166 563 -68 31359 -13718 97 -232 67 -2612
|
||||
RAW_Data: 165 -162 427 -166 199 -168 65 -132 133 -132 231 -66 699 -66 233 -100 56829 -10968 65 -100 325 -196 651 -1018 131 -1018 99 -428 131 -1126 131 -66 261 -98 427 -98 165 -334 365 -66 163 -66 525 -100 40845 -12094 65 -590 163 -1152 65 -1318 329 -594 263 -264 261 -264 2493 -66 465 -66 13953 -12826 131 -366 97 -1030 65 -700 67 -862 437 -326 187 -378 221 -348 221 -392 203 -380 219 -348 253 -348 223 -366 241 -320 249 -350 251 -350 233 -378 209 -352 251 -316 283 -320 271 -312 253 -344 251 -316 285 -336 241 -320 279 -314 281 -320 269 -312 251 -344 251 -312 287 -302 313 -282 277 -314 279 -320 271 -316 281 -278 311 -280 285 -342 283 -280 277 -314 277 -320 273 -318 281 -276 313 -280 285 -340 283 -280 277 -314 277 -320 273 -318 281 -278 311 -280 319 -274 319 -282 275 -314 277 -320 271 -318 281 -276 313 -278 287 -340 283 -282 275 -312 279 -320 273 -318 281 -276 313 -278 319 -310 283 -280 277 -314 277 -320 273 -318 281 -276 313 -280 285 -342 281 -280 277 -314 279 -320 273 -318 281 -278 311 -280 319 -274 317 -282 275 -314 279 -318 271 -318 281 -278 311 -280 285 -306 317 -282 277 -312 279 -320 271 -320 279 -278 311 -280 319 -308 283 -280 277 -312 279 -320 273 -318 281 -276 313 -280 285 -340 283 -282 277 -312 279 -322 273 -316 281 -278 311 -280 287 -304 317 -282 275 -314 279 -318 273 -318 281 -278 313 -278 285 -342 281 -282 275 -314 279 -318 273 -320 279 -278 313 -278 287 -340 283 -280 277 -314 279 -318 273 -318 281 -278 311 -280 319 -308 283 -282 275 -314 279 -320 273 -318 279 -278 311 -280 287 -304 317 -282 275 -314 279 -318 273 -320 279 -278 311 -280 285 -342 281 -282 275 -314 279 -320 273 -318 281 -276 313 -280 285 -342 281 -282 275 -314 279 -318 273 -320 279 -278 311 -280 319 -274 317 -284 273 -314 279 -320 273 -316 281 -276 311 -280 319 -308 283 -282 275 -314 279 -320 271 -320 281 -276 313 -278 287 -340 283 -280 277 -314 277 -320 273 -318 281 -276 313 -280 285 -342 281 -280 277 -314 279 -320 271 -318 281 -276 313 -280 319 -274 319 -282 273 -314 279 -320 273 -318 281 -276 313 -278 285 -342 281 -282 275 -314 279 -320 273 -318 281 -276 313 -278 285 -342 283 -280 277 -314 279 -318 273 -318 281 -278 311 -280 285 -342 281 -282 277 -312 279 -320 273 -318 281 -278 311 -280 319 -274 317 -282 275 -314 279 -318 271 -318 281 -278 313 -278 287 -606 577 -604 579 -602 577 -310 277 -310 281 -298
|
||||
RAW_Data: 313 -584 281 -322 289 -288 569 -288 311 -292 311 -588 281 -288 575 -308 319 -272 277 -322 273 -316 287 -288 287 -326 287 -290 287 -598 277 -322 563 -580 603 -586 317 -284 289 -288 569 -614 563 -316 285 -594 569 -618 585 -310 283 -574 607 -290 293 -274 319 -574 283 -308 281 -298 583 -608 573 -580 607 -282 309 -280 309 -280 297 -584 611 -272 305 -600 309 -286 573 -590 577 -604 581 -318 287 -570 293 -278 617 -282 325 -290 289 -580 591 -290 281 -598 579 -316 287 -290 289 -602 575 -602 283 -286 327 -288 291 -288 291 -290 577 -588 613 -276 317 -288 287 -596 281 -288 327 -290 575 -308 271 -104124 131 -500 97 -400 133 -1754 131 -1422 793 -134 267 -66 65 -100 229 -328 691 -98 1149 -12776 65 -696 97 -2270 99 -592 99 -164 295 -132 227 -198 195 -460 97 -100 329 -100 953 -98 4183 -12254 101 -2024 65 -66 65 -234 197 -430 163 -66 131 -68 331 -98 231 -100 65 -100 395 -134 15619 -12144 65 -166 97 -196 229 -164 65 -1454 131 -66 133 -464 925 -66 163 -232 867 -14370 131 -492 97 -330 1659 -100 65 -168 97 -100 3417 -12914 99 -1324 99 -1282 65 -458 163 -130 65 -462 65 -396 263 -198 231 -66 929 -270 297 -12864 65 -660 65 -462 99 -66 199 -790 131 -988 131 -66 99 -298 663 -66 197 -66 365 -66 499 -100 5013 -10396 65 -1116 165 -1262 99 -830 919 -100 659 -66 227 -132 923 -98 1029 -12218 99 -370 97 -364 65 -988 131 -66 1517 -12846 65 -368 99 -1660 67 -796 165 -134 165 -98 133 -200 427 -68 165 -68 501 -15932 131 -134 67 -300 131 -230 129 -328 97 -132 99 -1342 133 -464 297 -166 563 -66 991 -100 1127 -100 201 -66 3815 -13122 65 -432 165 -100 65 -594 657 -694 197 -426 63 -228 229 -164 429 -100 729 -166 34197 -10852 65 -1618 197 -328 65 -1478 99 -624 663 -100 231 -66 933 -66 54531 -14630 199 -1366 99 -298 331 -66 431 -98 299 -166 133 -264 165 -232 2329 -19750 331 -132 297 -66 163 -132 491 -200 2107 -13192 63 -756 65 -462 197 -1360 431 -66 99 -166 165 -66 197 -332 561 -166 67 -164 1093 -100 33695 -12824 131 -234 67 -564 233 -332 165 -266 165 -864 133 -430 297 -64 1709 -12180 131 -166 65 -98 163 -198 131 -66 393 -98 131 -230 65 -526 493 -164 81389 -14894 297 -330 265 -562 99 -234 133 -366 1119 -100 559 -66 28385 -258 303 -318 287 -290 327 -252 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -288 307 -276
|
||||
RAW_Data: 315 -274 321 -288 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -290 307 -274 317 -276 319 -290 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 287 -292 323 -242 305 -284 337 -286 287 -288 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 289 -290 307 -276 315 -274 321 -288 291 -290 325 -254 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 323 -242 305 -284 341 -288 287 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -288 325 -272 275 -316 309 -288 287 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 327 -256 325 -256 327 -290 291 -290 289 -288 325 -242 305 -284 339 -286 289 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 323 -254 305 -278 317 -274 321 -288 289 -290 325 -254 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 325 -252 307 -278 317 -276 321 -288 291 -290 327 -254 327 -256 325 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 287 -290 307 -274 319 -274 321 -290 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -578 619 -552 607 -588 603 -274 323 -274 275 -318 309 -556 319 -292 291 -290 579 -312 289 -290 291 -592 291 -298 579 -320 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -572 291 -308 591 -282 325 -290 289 -292 289 -292 327 -254 327 -254 327 -292 289 -570 287 -308 293 -314 585 -558 597 -294 307 -590 581 -284 325 -568 311 -288 595 -258 323 -276 321 -288 291 -290 291 -290 327 -254 327 -292 289 -292 291 -290 327 -254 327 -256 325 -574 309 -272 595 -276 321 -558 613 -590 309 -272 277 -286 343 -286 289 -288 587 -592 311 -290 563 -578 305 -284
|
||||
RAW_Data: 593 -320 273 -278 307 -286 339 -548 319 -274 597 -294 299 -580 615 -564 291 -276 617 -286 323 -292 289 -548 313 -278 311 -296 583 -320 287 -108256 65 -1660 165 -1562 131 -1956 131 -100 197 -132 365 -166 635 -264 131 -166 2123 -12356 65 -398 165 -296 99 -3000 761 -596 131 -458 65 -132 97 -98 263 -64 1543 -98 32171 -3880 65 -8206 65 -528 97 -890 65 -130 99 -296 97 -656 163 -98 197 -98 819 -132 63 -132 13561 -13002 99 -1616 97 -490 231 -264 131 -196 197 -98 163 -298 921 -200 165 -134 265 -132 4349 -10926 99 -1386 197 -166 97 -460 131 -1414 165 -296 263 -100 365 -298 131 -66 233 -364 263 -228 1637 -12078 65 -1182 97 -132 165 -458 97 -164 129 -1052 131 -460 131 -362 359 -132 591 -66 463 -396 1485 -15694 131 -1220 65 -528 163 -298 165 -702 263 -100 97 -198 65 -228 461 -98 425 -132 131 -66 263 -132 995 -15974 97 -366 99 -1550 65 -360 197 -528 461 -66 823 -166 431 -66 99 -134 44023 -10470 99 -1524 133 -296 65 -594 65 -364 197 -598 363 -100 333 -398 131 -334 97 -264 131 -68 925 -66 12671 -12638 265 -726 97 -130 65 -532 165 -328 99 -722 67 -168 233 -68 497 -200 65 -100 163 -164 459 -132 65 -262 97 -66 723 -66 623 -132 13701 -10162 165 -168 67 -334 131 -1228 131 -200 231 -130 133 -166 199 -200 465 -98 165 -134 199 -66 1649 -12528 63 -790 99 -2178 129 -66 131 -262 65 -132 63 -132 263 -200 623 -64 99 -98 557 -134 131 -132 199 -132 335 -132 14767 -12244 65 -1624 63 -1480 131 -888 529 -268 629 -298 233 -98 65 -100 495 -100 11243 -12044 133 -332 65 -366 65 -1162 65 -98 129 -1128 97 -954 229 -460 65 -100 231 -234 497 -100 12295 -12466 129 -886 97 -2882 331 -134 333 -600 263 -332 433 -100 65 -100 163 -130 721 -98 32991 -12088 379 -290 289 -290 287 -308 275 -318 273 -320 287 -290 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -290 327 -290 291 -290 289 -288 287 -290 305 -276 317 -272 319 -288 287 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 289 -290 287 -290 307 -278 315 -272 317 -286 291 -288 327 -288 291 -290 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 291 -290 287 -290 287 -324 271 -278 317 -274 319 -288 289 -290 291 -326 291 -292 289 -292 289 -292 291 -290 327 -254 327 -290 289 -290 289 -292 291 -290 327 -288
|
||||
RAW_Data: 289 -290 289 -290 287 -290 307 -276 317 -272 319 -288 289 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 289 -288 287 -278 307 -284 337 -282 287 -288 327 -252 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -290 287 -290 287 -308 277 -284 305 -320 287 -288 289 -290 327 -292 289 -290 291 -290 291 -288 327 -290 289 -290 289 -292 291 -290 291 -290 327 -292 289 -292 289 -290 287 -290 287 -308 277 -318 271 -320 287 -290 287 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -288 289 -288 289 -288 323 -272 275 -288 305 -318 287 -290 289 -290 325 -292 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -288 289 -288 289 -324 271 -276 319 -274 319 -288 287 -290 327 -290 291 -288 291 -290 289 -290 325 -292 291 -288 291 -290 291 -292 289 -292 289 -328 291 -290 291 -288 289 -288 287 -308 277 -316 273 -320 287 -288 289 -292 325 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -290 325 -290 287 -290 287 -288 305 -278 285 -304 317 -288 287 -290 289 -328 291 -290 291 -292 289 -292 289 -292 291 -602 591 -576 599 -582 571 -286 307 -284 305 -320 287 -594 283 -288 289 -328 573 -274 305 -278 319 -576 317 -290 581 -276 323 -290 287 -290 287 -324 271 -278 317 -274 319 -288 289 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -602 313 -286 563 -292 283 -306 317 -558 317 -288 579 -588 613 -274 319 -286 289 -288 327 -252 327 -288 291 -292 289 -292 289 -292 291 -290 327 -292 289 -572 581 -296 301 -582 317 -276 599 -290 293 -578 317 -276 277 -310 577 -316 279 -308 281 -600 575 -612 593 -260 311 -296 315 -550 315 -276 597 -294 301 -580 613 -560 287 -310 593 -320 273 -566 321 -296 575 -580 601 -578 303 -316 249 -308 311 -278 609 -284 279 -310 279 -604 311 -286 279 -278 629 -266 305 -112108 65 -822 65 -262 97 -1814 65 -296 99 -196 163 -164 99 -130 65 -396 195 -66 197 -98 163 -296 65 -330 1357 -100 21063 -12594 131 -700 97 -496 165 -100 99 -266 131 -134 199 -300 395 -132 633 -164 661 -66 62501 -13474 65 -164 97 -230 129 -396 163 -1416 593 -264 761 -498 495 -100 38361 -11648 65 -930 65 -332 133 -100 165 -730 65 -330 131 -524
|
||||
RAW_Data: 165 -890 129 -196 65 -196 99 -164 163 -198 263 -132 365 -66 561 -198 589 -66 126915 -10010 65 -1958 97 -100 263 -398 131 -360 99 -460 197 -164 165 -950 99 -458 391 -66 231 -100 261 -860 825 -15130 131 -132 65 -464 65 -1098 65 -200 133 -168 165 -68 99 -666 1897 -100 365 -100 29541 -10798 65 -688 65 -954 131 -166 133 -498 299 -400 165 -398 99 -332 465 -200 60683 -12126 97 -628 133 -566 131 -1028 267 -100 227 -66 165 -232 563 -134 235 -12052 133 -562 165 -460 99 -298 165 -862 197 -264 395 -134 65 -100 331 -300 733 -132 395 -132 67 -66 131 -166 1479 -64 14715 -12440 131 -2222 131 -68 65 -494 663 -66 663 -166 3725 -12418 131 -466 231 -730 415 -254 279 -310 283 -312 257 -340 281 -284 277 -314 279 -320 271 -316 279 -278 313 -280 285 -342 281 -282 277 -312 279 -320 271 -320 281 -276 313 -280 285 -342 281 -282 275 -312 279 -320 273 -318 281 -278 311 -280 285 -342 281 -282 277 -312 279 -320 273 -318 281 -278 311 -280 319 -274 317 -282 277 -314 277 -320 271 -318 281 -276 313 -278 287 -340 283 -280 277 -314 277 -320 273 -318 279 -278 313 -280 285 -340 283 -280 277 -314 277 -320 273 -320 281 -276 313 -280 285 -340 283 -280 277 -314 277 -320 273 -318 279 -278 311 -280 289 -306 315 -282 277 -312 279 -320 271 -318 281 -278 313 -278 287 -340 283 -280 277 -314 279 -318 273 -318 281 -276 313 -280 285 -340 283 -282 275 -314 279 -318 273 -320 279 -278 313 -280 285 -340 283 -282 277 -312 279 -322 273 -316 279 -280 311 -280 285 -306 317 -282 275 -314 279 -318 273 -318 279 -278 313 -280 285 -340 283 -280 277 -312 279 -320 273 -320 281 -276 313 -278 285 -342 281 -282 277 -314 277 -320 273 -318 281 -278 311 -280 319 -274 317 -282 275 -314 279 -320 273 -316 281 -278 311 -280 285 -340 283 -282 275 -314 279 -318 273 -318 281 -276 313 -278 287 -340 283 -282 277 -312 279 -320 273 -318 279 -278 311 -280 285 -342 281 -282 277 -314 277 -320 273 -318 279 -278 313 -280 319 -274 317 -282 277 -314 279 -318 271 -318 279 -278 311 -280 285 -342 283 -280 277 -314 277 -320 273 -318 281 -276 313 -280 285 -340 283 -282 275 -314 279 -318 273 -318 281 -278 313 -278 287 -340 283 -280 277 -314 277 -320 273 -318 281 -278 311 -280 319 -274 317 -282 277 -314 277 -320 271 -318 279 -278 313 -280 285 -340 283 -280 277 -314 279 -318 273 -318 281 -278 311 -280 285 -340 283 -282 275 -314 279 -320 271 -318 281 -278 311 -280
|
||||
RAW_Data: 285 -342 283 -280 277 -314 277 -322 273 -318 279 -278 311 -280 319 -308 283 -282 275 -314 277 -320 271 -318 281 -278 313 -278 287 -340 283 -282 277 -314 277 -320 271 -318 281 -278 311 -280 287 -340 283 -280 277 -314 277 -320 273 -316 281 -278 313 -278 319 -308 283 -576 589 -584 601 -574 603 -308 269 -278 321 -312 283 -588 281 -322 289 -288 571 -308 271 -312 291 -580 315 -278 599 -292 293 -312 283 -282 275 -314 279 -318 273 -318 281 -278 311 -280 285 -608 575 -606 291 -284 601 -314 289 -288 287 -600 275 -322 563 -290 287 -304 317 -588 283 -286 575 -618 583 -578 317 -286 287 -290 575 -310 285 -594 587 -614 577 -312 285 -564 581 -306 317 -284 289 -592 281 -324 571 -308 271 -278 323 -310 283 -286 287 -324 289 -560 601 -582 293 -300 579 -586 313 -286 567 -614 299 -276 585 -316 277 -310 279 -614 579 -282 275 -312 279 -324 275 -614 283 -276 311 -280 579 -616 573 -308 285 -322 271 -278 319 -576 317 -286 287 -288 577 -310 283 -109908 131 -428 131 -266 99 -430 129 -132 65 -366 97 -1564 165 -362 461 -198 559 -132 99 -102 4577 -12096 131 -162 129 -132 65 -728 165 -1514 165 -232 131 -232 97 -198 895 -12322 65 -402 97 -1528 99 -434 99 -98 1097 -132 133 -232 635 -12246 65 -166 165 -764 131 -198 135 -1096 65 -800 65 -164 197 -64 425 -66 365 -66 431 -98 2069 -15134 99 -428 165 -396 167 -762 131 -630 163 -368 99 -466 595 -100 463 -100 131 -68 197 -100 267 -98 857 -100 863 -66 1899 -12484 99 -834 165 -1390 99 -100 97 -68 363 -166 99 -132 197 -894 1091 -66 99 -130 561 -132 20453 -9860 65 -2544 263 -894 65 -460 133 -792 99 -634 555 -100 97 -298 297 -164 327 -66 131 -98 22573 -12182 67 -264 133 -496 97 -466 163 -1850 65 -100 327 -98 167 -330 131 -168 199 -166 333 -298 2059 -98 80177 -11990 67 -298 165 -434 133 -1986 99 -724 161 -130 163 -66 297 -66 199 -266 399 -132 331 -100 365 -12186 197 -634 133 -598 263 -230 197 -68 99 -66 65 -400 65 -166 265 -132 10227 -12682 99 -362 67 -1158 97 -1152 199 -232 433 -266 265 -332 97 -134 865 -130 21249 -12400 65 -1452 163 -166 131 -468 231 -890 99 -800 165 -132 199 -402 131 -66 65 -132 1383 -66 919 -12188 99 -66 131 -796 65 -396 167 -692 197 -132 789 -132 54741 -12962 65 -1370 65 -134 65 -898 65 -168 199 -68 297 -132 65 -334 163 -460 861 -15230 65 -66 65 -1584 97 -396 131 -328 161 -66 129 -164 297 -132 195 -132 165 -100 1629 -12166 99 -332
|
||||
RAW_Data: 199 -266 365 -590 165 -332 63 -328 131 -522 361 -198 131 -530 263 -130 429 -68 595 -232 459 -98 165 -66 53791 -282 309 -274 313 -276 319 -290 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -292 291 -290 291 -288 307 -276 315 -276 321 -288 291 -290 291 -292 325 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -290 325 -242 305 -284 301 -318 289 -288 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -290 307 -276 315 -274 321 -288 291 -288 327 -252 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -288 325 -272 275 -316 309 -288 287 -292 291 -290 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -288 309 -274 315 -274 319 -290 287 -292 289 -292 327 -290 291 -292 289 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -290 325 -242 305 -284 303 -320 287 -290 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 287 -290 307 -276 319 -274 321 -286 289 -290 327 -252 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -288 325 -272 275 -318 309 -286 289 -290 291 -290 327 -256 325 -292 291 -290 291 -290 327 -256 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 289 -290 307 -274 315 -274 321 -288 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 327 -256 325 -256 327 -290 291 -290 289 -288 287 -290 307 -276 315 -276 319 -290 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -540 615 -584 577 -588 601 -310 271 -276 321 -278 323 -558 319 -290 289 -292 611 -276 321 -242 307 -580 317 -288 587 -318 287 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -564 611 -276 321 -290 289 -292 289 -598 573 -588 613 -274 319 -560 611 -560 609 -302
|
||||
RAW_Data: 313 -550 611 -272 307 -564 607 -284 309 -278 309 -572 317 -280 305 -280 309 -294 311 -288 573 -588 289 -286 303 -318 575 -272 305 -276 323 -578 319 -274 277 -308 609 -586 283 -290 611 -276 321 -274 275 -286 341 -284 289 -290 289 -588 573 -584 613 -278 321 -560 613 -278 323 -292 291 -290 291 -290 291 -596 583 -276 321 -558 319 -290 289 -292 611 -276 323 -242 305 -288 341 -554 317 -292 291 -290 579 -314 287 -109620 197 -562 131 -166 271 -132 231 -1018 65 -1186 97 -132 459 -264 229 -294 197 -66 1149 -132 28783 -6568 197 -264 199 -100 99 -830 97 -100 493 -66 499 -100 43647 -10992 65 -956 63 -796 131 -500 99 -66 229 -64 195 -164 1219 -132 2037 -66 2069 -12156 63 -1980 99 -66 99 -926 165 -562 65 -398 195 -164 327 -196 361 -132 163 -100 195 -164 489 -20064 165 -132 297 -164 197 -130 15037 -11748 99 -1188 99 -822 97 -2376 431 -232 99 -298 265 -66 133 -264 131 -100 99 -166 1559 -100 4971 -12792 231 -1390 431 -364 231 -464 263 -134 325 -264 22655 -11300 133 -328 65 -1380 197 -494 195 -264 67 -164 163 -100 97 -460 295 -1088 65 -198 261 -66 131 -196 131 -64 263 -328 329 -198 529 -296 963 -13076 133 -698 65 -1050 99 -164 65 -1066 131 -132 65 -66 229 -294 197 -66 625 -426 919 -13966 99 -394 99 -918 99 -434 131 -100 333 -100 99 -166 65 -134 829 -166 1621 -98 54473 -15020 65 -98 65 -164 165 -366 65 -460 495 -200 131 -68 165 -334 65 -168 333 -166 65 -166 463 -166 729 -132 99 -132 24865 -10390 65 -1618 99 -398 97 -496 131 -724 97 -1022 99 -298 65 -166 231 -362 233 -232 165 -232 327 -296 461 -198 921 -66 30107 -266 289 -338 281 -284 283 -322 287 -288 285 -322 251 -324 285 -288 285 -324 289 -288 287 -322 269 -278 285 -340 283 -286 287 -324 289 -288 289 -290 287 -326 251 -326 287 -290 287 -290 289 -324 289 -288 287 -286 275 -310 283 -306 315 -286 287 -322 291 -288 289 -288 291 -290 287 -326 287 -290 287 -290 289 -324 289 -288 287 -288 287 -306 277 -318 271 -318 285 -288 287 -326 287 -290 287 -290 289 -324 289 -288 289 -288 289 -324 287 -288 287 -288 285 -308 277 -320 271 -318 285 -286 325 -288 289 -288 291 -290 287 -324 289 -288 289 -288 289 -324 287 -288 287 -288 285 -308 277 -318 271 -318 285 -288 287 -326 287 -290 287 -290 287 -326 287 -290 289 -288 289 -324 289 -288 287 -288 287 -306 277 -318 271 -318 283 -286 325 -288 287 -290 289 -288 323 -288 289 -288 289 -288 289 -324 289 -288
|
||||
RAW_Data: 285 -288 275 -308 285 -304 317 -286 285 -326 287 -290 287 -290 287 -326 251 -326 289 -288 289 -288 289 -324 287 -288 287 -324 269 -278 285 -340 283 -284 287 -324 289 -288 287 -290 287 -324 289 -288 289 -288 287 -326 287 -288 287 -286 287 -306 277 -318 273 -318 285 -286 287 -326 287 -290 287 -290 287 -326 287 -290 289 -288 289 -324 287 -288 287 -288 285 -306 277 -320 273 -318 285 -288 287 -326 287 -288 289 -288 287 -326 287 -288 289 -288 287 -324 287 -288 285 -322 269 -278 287 -340 283 -286 287 -324 289 -288 289 -288 289 -326 251 -326 287 -290 287 -290 287 -326 287 -288 285 -324 269 -278 285 -306 317 -286 285 -326 251 -326 289 -288 289 -324 287 -288 289 -288 289 -324 287 -288 287 -286 285 -322 271 -280 319 -310 281 -286 287 -326 287 -290 287 -290 287 -326 251 -326 289 -288 289 -288 289 -324 287 -288 287 -322 271 -276 287 -338 283 -286 287 -324 289 -288 289 -290 287 -326 287 -290 287 -288 289 -324 251 -324 287 -286 285 -308 277 -318 271 -316 285 -288 287 -326 287 -290 287 -290 287 -326 289 -288 289 -288 287 -324 287 -288 287 -288 275 -308 283 -304 315 -286 287 -324 289 -288 289 -288 289 -324 289 -288 289 -288 287 -324 287 -564 581 -604 577 -606 575 -302 277 -320 285 -288 289 -594 315 -286 289 -288 569 -322 281 -298 313 -584 281 -278 599 -294 301 -314 285 -282 275 -314 279 -320 273 -318 285 -288 325 -558 603 -584 587 -580 577 -312 285 -598 289 -286 599 -586 313 -286 569 -612 561 -616 573 -590 579 -306 315 -584 575 -590 289 -286 601 -316 287 -596 279 -286 601 -580 301 -312 283 -288 285 -326 251 -326 287 -290 579 -588 291 -286 599 -318 285 -290 289 -288 289 -600 587 -580 303 -316 285 -286 583 -586 587 -292 285 -606 577 -312 285 -600 577 -602 577 -604 583 -290 295 -312 285 -286 285 -326 289 -288 289 -288 289 -324 253 -600 307 -286 285 -308 565 -306 315 -110240 231 -430 263 -366 131 -266 67 -728 231 -100 133 -166 297 -364 99 -166 399 -332 429 -66 4277 -5496 99 -2222 65 -500 331 -360 461 -98 563 -166 131 -98 99 -266 597 -15902 99 -296 129 -658 65 -1194 589 -164 99 -434 131 -134 20045 -13086 163 -100 661 -230 299 -168 1427 -12238 67 -464 131 -298 131 -368 131 -600 133 -364 131 -856 65 -358 821 -132 431 -164 787 -64 859 -12514 99 -100 65 -630 99 -2328 661 -164 65 -132 825 -100 1525 -100 1595 -15922 459 -66 165 -134 365 -298 393 -166 2147 -66 25665 -10134 65 -932 97 -330 131 -2324
|
||||
RAW_Data: 661 -100 97 -66 329 -198 195 -198 122267 -7020 297 -364 267 -6902 133 -394 131 -890 1713 -166 65 -98 65 -100 1681 -66 8821 -16442 99 -66 857 -232 135 -66 499 -100 2103 -262 1613 -13004 99 -1482 163 -64 163 -164 65 -132 163 -296 427 -564 2331 -66 76439 -12154 229 -526 197 -396 67 -2688 65 -232 265 -196 197 -68 1295 -100 199 -166 1811 -100 2509 -12186 133 -430 131 -164 97 -424 97 -132 97 -98 63 -726 197 -590 65 -624 199 -132 233 -100 67 -200 165 -764 165 -100 333 -66 1831 -68 11129 -13010 99 -1090 523 -164 427 -64 2297 -19818 63 -166 359 -132 131 -262 129 -132 195 -66 16739 -13200 99 -228 63 -428 131 -1282 131 -198 129 -490 99 -294 165 -394 163 -250 321 -310 283 -284 275 -314 279 -318 273 -318 279 -278 313 -278 285 -342 281 -282 277 -314 277 -320 273 -318 281 -276 313 -280 285 -340 283 -280 277 -312 279 -320 273 -318 281 -276 313 -280 285 -342 281 -282 275 -314 279 -320 275 -318 279 -278 313 -280 317 -274 317 -282 275 -314 279 -318 271 -318 281 -278 313 -278 287 -340 283 -280 277 -314 277 -320 273 -318 281 -278 311 -280 285 -340 283 -282 275 -314 279 -318 273 -318 281 -278 313 -278 287 -340 281 -282 277 -312 279 -320 273 -318 279 -278 313 -280 319 -274 317 -282 275 -314 277 -320 271 -318 281 -278 313 -278 287 -340 283 -280 277 -314 277 -320 273 -318 281 -278 311 -280 285 -342 281 -282 277 -314 277 -320 273 -318 279 -278 313 -280 317 -308 283 -282 275 -314 279 -320 273 -318 279 -278 311 -280 287 -340 281 -282 277 -314 277 -320 273 -318 279 -278 313 -280 285 -340 283 -282 275 -314 279 -318 273 -318 281 -278 311 -280 285 -342 281 -282 277 -314 277 -320 271 -318 281 -278 313 -278 319 -308 283 -282 277 -314 277 -322 271 -318 279 -278 311 -280 285 -342 281 -282 277 -312 279 -320 271 -320 281 -276 313 -280 285 -340 283 -282 275 -314 279 -318 273 -320 279 -278 313 -278 287 -340 281 -282 277 -314 277 -320 273 -318 279 -278 311 -280 321 -308 283 -282 275 -314 279 -320 271 -318 279 -278 313 -280 285 -340 283 -280 277 -314 277 -320 273 -318 281 -276 313 -280 285 -342 281 -282 277 -312 279 -320 271 -318 281 -278 313 -278 287 -340 283 -280 277 -314 279 -320 271 -318 279 -280 311 -280 321 -272 317 -282 275 -314 279 -320 271 -318 281 -278 313 -278 287 -338 283 -282 277 -314 277 -320 271 -318 281 -278 313 -278 287 -340 281 -282 277 -314 277 -320 271 -318 281 -278 311 -280 287 -340 283 -282
|
||||
RAW_Data: 275 -314 279 -320 273 -316 279 -280 311 -282 285 -306 317 -282 277 -312 279 -320 271 -318 281 -276 313 -280 285 -342 281 -282 277 -314 277 -320 271 -318 281 -278 313 -278 285 -342 281 -282 277 -312 279 -320 273 -318 281 -276 313 -280 285 -340 283 -282 277 -312 279 -618 579 -572 585 -618 573 -318 285 -290 287 -290 289 -600 273 -322 287 -288 593 -292 297 -314 283 -590 281 -322 539 -342 269 -278 287 -342 283 -286 287 -324 289 -288 289 -288 289 -326 287 -288 289 -568 613 -562 313 -284 583 -316 285 -568 607 -600 579 -318 285 -564 309 -284 593 -292 297 -580 315 -286 579 -310 285 -594 583 -312 283 -286 287 -596 279 -322 287 -290 287 -326 289 -288 563 -584 305 -316 283 -282 603 -290 279 -600 577 -314 285 -602 577 -600 579 -588 589 -580 601 -584 569 -310 285 -592 295 -300 315 -284 575 -300 277 -312 295 -576 317 -276 601 -292 295 -312 283 -574 287 -308 575 -312 283 -324 289 -288 289 -596 273 -308 277 -286 603 -316 287 -107414 65 -296 65 -656 99 -1316 229 -298 131 -496 97 -1058 99 -600 165 -230 97 -66 495 -66 197 -232 133 -198 133 -166 199 -100 17061 -12074 97 -294 591 -362 163 -2208 227 -430 429 -664 691 -66 131 -66 295 -296 199 -134 39413 -12146 65 -430 65 -464 99 -166 131 -266 133 -2216 65 -66 163 -98 467 -198 495 -164 269 -166 15617 -12040 127 -592 129 -364 65 -690 97 -430 563 -66 65 -134 99 -398 131 -266 165 -166 1091 -100 499 -66 3503 -14492 133 -924 1025 -100 199 -134 167 -66 199 -132 397 -66 20637 -13320 197 -1600 99 -1624 199 -262 165 -402 131 -66 99 -232 165 -100 265 -198 299 -66 433 -10646 129 -262 65 -460 99 -492 65 -2322 133 -300 1565 -134 99 -100 165 -66 431 -12514 99 -302 131 -232 99 -664 65 -426 65 -1452 131 -132 227 -66 523 -134 65 -130 561 -12722 65 -166 131 -334 99 -100 99 -428 331 -198 99 -134 201 -66 991 -262 131 -362 197 -132 167 -16668 99 -198 65 -300 165 -464 65 -432 65 -532 263 -102 165 -300 99 -332 99 -198 427 -134 563 -100 265 -100 20409 -12332 131 -950 65 -564 131 -200 429 -66 131 -100 795 -394 427 -66 4773 -12096 101 -332 163 -464 99 -930 129 -68 99 -1196 791 -200 99 -298 429 -264 161 -132 265 -68 15137 -13228 99 -370 231 -166 131 -334 65 -200 199 -100 65 -100 65 -164 233 -232 1855 -96 16429 -12068 99 -266 67 -364 65 -368 99 -330 165 -996 229 -132 65 -996 165 -200 97 -166 99 -134 231 -166 299 -168 165 -68 83017 -5992 389 -268 315 -286
|
||||
RAW_Data: 287 -288 289 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 289 -288 289 -288 307 -278 315 -274 319 -286 289 -288 327 -290 289 -290 289 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 289 -290 289 -288 289 -308 275 -318 271 -320 287 -290 287 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -288 291 -290 289 -290 323 -290 287 -290 287 -288 287 -308 275 -320 271 -320 287 -290 287 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -290 291 -292 289 -292 289 -326 291 -290 289 -288 289 -288 307 -276 319 -272 319 -286 289 -288 325 -290 291 -288 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 289 -288 289 -288 307 -276 317 -274 317 -288 287 -290 289 -328 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 289 -290 289 -290 325 -290 287 -290 289 -288 287 -288 307 -278 317 -272 319 -288 287 -290 289 -328 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -288 289 -288 287 -308 277 -284 305 -318 287 -288 325 -290 289 -290 291 -288 291 -288 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 289 -326 289 -290 287 -290 287 -324 271 -276 319 -274 319 -288 289 -288 291 -328 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 289 -290 291 -288 327 -252 325 -290 287 -290 287 -288 307 -276 283 -306 317 -286 289 -288 327 -292 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 287 -290 289 -288 323 -272 275 -320 307 -286 287 -288 325 -254 325 -292 289 -290 291 -290 291 -290 291 -328 289 -292 291 -290 291 -290 291 -292 289 -292 325 -290 287 -290 287 -290 287 -308 275 -320 273 -318 287 -290 287 -292 325 -292 291 -290 291 -290 291 -292 289 -292 325 -290 291 -288 291 -288 291 -288 325 -290 287 -290 287 -286 307 -276 285 -606 579 -588 583 -614 577 -318 287 -288 291 -290 291 -602 311 -288 287 -290 593 -290 293 -310 285 -592 283 -288 575 -310 321 -272 277 -320 307 -284 287 -286 327 -252 327 -288 291 -564 313 -288 289 -288 325 -288 563 -292 287 -304 317 -590 283 -286 577 -620 581 -578 577 -622 583 -290 291 -580 577 -622 581 -586 277 -320 285 -290 583 -590 587 -292 285 -604 577 -316 287 -290 287 -326 287 -290
|
||||
RAW_Data: 287 -290 287 -306 277 -614 579 -282 321 -572 309 -272 601 -576 317 -288 579 -588 289 -284 301 -318 589 -574 589 -582 601 -584 281 -324 289 -288 571 -292 309 -294 311 -554 317 -286 291 -326 573 -274 305 -278 317 -274 319 -286 287 -586 315 -288 287 -290 601 -288 281 -112244 131 -530 65 -456 65 -198 161 -1382 131 -200 297 -134 265 -168 133 -132 1363 -100 24465 -7930 229 -330 97 -98 63 -3472 99 -992 65 -430 97 -1950 99 -654 851 -98 393 -692 1943 -64 5697 -13412 67 -562 265 -134 297 -100 97 -198 461 -9818 67 -2320 165 -98 99 -298 99 -426 97 -24386 99 -396 99 -100 97 -426 65 -296 97 -130 99 -298 163 -66 393 -164 463 -396 97 -98 1023 -66 753 -12424 65 -100 163 -1300 99 -66 131 -66 327 -66 65 -98 493 -66 723 -66 56389 -9914 165 -362 97 -360 131 -456 65 -100 65 -132 595 -98 97 -362 231 -66 265 -132 523 -134 20909 -16716 165 -664 99 -266 99 -596 265 -132 165 -130 881 -230 4539 -12892 97 -720 65 -688 99 -592 401 -166 431 -198 99 -134 165 -66 167 -100 165 -200 64771 -12822 163 -1130 97 -458 261 -198 327 -66 165 -330 165 -294 461 -132 11025 -12170 131 -298 97 -820 99 -628 133 -466 131 -462 99 -466 165 -300 165 -166 165 -200 267 -66 101 -364 99 -396 14089 -12496 63 -1220 165 -424 99 -688 363 -728 65 -166 67 -100 331 -398 97 -132 99 -562 531 -102 1295 -66 32909 -12886 99 -860 163 -1390 165 -168 165 -66 563 -100 397 -164 261 -498 1787 -12136 65 -164 229 -1350 129 -1348 131 -134 597 -166 133 -66 365 -132 993 -66 501 -66 3521 -12280 557 -132 295 -98 65 -1162 133 -298 99 -166 133 -66 99 -166 267 -66 329 -164 195 -198 397 -68 33215 -4820 381 -288 289 -288 289 -290 287 -288 307 -276 317 -276 317 -288 289 -288 325 -290 289 -290 291 -288 291 -288 327 -290 289 -290 289 -292 291 -290 291 -290 327 -292 289 -290 287 -290 287 -324 271 -278 317 -274 319 -288 289 -288 291 -326 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 289 -290 291 -288 325 -288 289 -288 287 -278 307 -286 299 -316 287 -288 287 -292 325 -292 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 289 -326 289 -288 289 -288 287 -324 271 -276 319 -276 319 -286 287 -290 327 -290 291 -288 291 -290 289 -290 325 -290 291 -288 291 -288 291 -292 289 -328 287 -290 287 -290 287 -288 307 -278 283 -306 317 -288 289 -288 291 -326 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292
|
||||
RAW_Data: 289 -292 291 -290 291 -288 325 -288 289 -288 287 -288 305 -278 317 -272 317 -286 287 -290 325 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -326 287 -290 287 -290 287 -324 271 -276 319 -274 319 -288 287 -290 289 -328 291 -290 291 -292 289 -290 289 -290 327 -288 291 -288 291 -288 289 -290 323 -290 287 -290 287 -288 305 -278 317 -274 317 -286 287 -290 327 -290 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -290 287 -326 287 -290 289 -288 289 -286 307 -278 317 -274 319 -286 287 -290 325 -292 289 -290 291 -288 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -292 325 -290 287 -290 287 -288 287 -308 275 -320 273 -320 285 -290 287 -328 291 -290 291 -290 291 -292 289 -292 289 -292 327 -288 291 -288 291 -290 289 -290 323 -290 287 -290 287 -278 307 -282 301 -318 285 -288 289 -328 289 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -290 289 -326 287 -290 287 -290 287 -288 305 -278 317 -274 317 -286 289 -288 325 -290 291 -288 291 -288 291 -290 325 -290 289 -292 291 -288 289 -288 325 -288 289 -288 289 -288 307 -276 283 -306 317 -286 287 -290 323 -292 289 -290 291 -290 291 -292 289 -604 589 -578 599 -580 573 -308 321 -272 277 -322 273 -588 317 -288 287 -292 577 -310 285 -322 271 -600 275 -318 585 -284 285 -324 289 -288 289 -288 289 -326 287 -290 287 -288 287 -594 587 -580 609 -276 321 -290 287 -596 259 -324 275 -320 583 -588 275 -322 563 -614 273 -318 555 -318 287 -578 585 -292 289 -608 579 -314 287 -602 289 -282 595 -318 287 -288 289 -292 289 -582 587 -582 603 -318 287 -290 287 -290 289 -574 607 -588 577 -316 287 -600 275 -324 287 -290 287 -290 287 -322 559 -296 299 -316 283 -592 577 -590 289 -286 299 -316 585 -588 277 -322 565 -580 305 -316 285 -280 603 -290 281 -296 315 -284 279 -310 279 -280 323 -578 317 -274 279 -310 575 -314 285 -107990 67 -400 67 -1956 99 -266 133 -134 599 -200 233 -166 99 -662 561 -68 1029 -15196 65 -200 165 -400 199 -758 65 -492 65 -1056 131 -654 429 -66 461 -690 1283 -66 783 -15472 265 -100 165 -1000 99 -132 67 -398 133 -864 461 -132 165 -232 863 -66 67 -164 231 -66 44933 -12120 131 -1760 97 -200 99 -400 67 -498 199 -300 97 -262 197 -98 99 -164 459 -198 99 -362 359 -98 129 -98 851 -15058 165 -400 65 -532 99 -300 131 -566 233 -1394 99 -266 99 -132
|
||||
RAW_Data: 131 -100 131 -200 199 -100 267 -298 365 -198 233 -66 47639 -12138 65 -536 99 -1920 131 -630 495 -232 723 -162 195 -68 31879 -14392 431 -132 701 -630 329 -234 231 -398 65 -332 131 -230 97 -132 1849 -66 42713 -12404 165 -66 99 -432 199 -358 131 -958 165 -66 823 -164 459 -198 1363 -100 299 -15824 197 -664 67 -268 133 -200 165 -1960 363 -100 131 -132 263 -66 99 -264 265 -232 433 -66 399 -12020 197 -134 67 -400 197 -498 199 -496 131 -66 133 -266 65 -830 397 -202 763 -66 199 -168 65 -166 1233 -132 38019 -12222 65 -66 65 -296 99 -1890 97 -200 231 -134 131 -198 199 -166 131 -132 133 -166 363 -15110 131 -464 99 -426 131 -796 65 -594 97 -902 65 -166 67 -66 199 -166 365 -166 199 -168 65 -100 599 -15138 99 -462 99 -264 65 -302 199 -166 97 -1432 65 -564 99 -334 399 -332 363 -166 363 -196 163 -98 99 -130 327 -66 265 -68 861 -66 12289 -12242 99 -168 167 -800 165 -2012 129 -722 131 -198 491 -66 165 -132 99 -166 65 -232 133 -200 1359 -15916 131 -430 99 -232 197 -952 131 -994 65 -296 65 -166 195 -66 199 -166 133 -168 133 -100 363 -296 327 -68 761 -14252 97 -430 131 -332 99 -1360 99 -466 131 -268 99 -268 65 -432 65 -232 801 -15156 97 -1124 67 -132 65 -232 97 -264 97 -460 297 -1326 333 -232 231 -68 131 -100 199 -100 67 -168 65 -66 197 -398 395 -100 897 -98 467 -68 333 -12482 229 -858 261 -132 97 -592 331 -66 431 -132 299 -66 359 -230 6513 -12296 129 -460 99 -232 99 -232 231 -864 99 -298 97 -526 97 -854 229 -66 65 -134 563 -266 99 -198 331 -166 2157 -100 15057 -12222 65 -626 97 -626 99 -294 131 -1156 67 -430 63 -660 129 -66 197 -66 129 -428 557 -100 22131 -12728 67 -200 99 -1130 387 -326 217 -350 209 -386 207 -384 209 -370 219 -388 201 -380 241 -352 209 -352 245 -350 243 -350 237 -346 223 -366 241 -350 245 -354 245 -320 245 -352 247 -352 247 -304 281 -322 271 -312 281 -320 247 -356 249 -320 285 -318 249 -318 283 -318 237 -344 257 -340 281 -282 285 -324 287 -286 287 -322 251 -322 287 -322 249 -322 285 -322 239 -310 285 -302 315 -286 287 -324 289 -288 289 -288 289 -290 291 -328 289 -292 291 -290 291 -290 291 -290 325 -290 287 -290 287 -286 307 -278 315 -274 317 -286 289 -288 291 -328 289 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 289 -288 289 -288 307 -276 285 -304 317 -288 289 -288 327 -288 291 -288
|
||||
RAW_Data: 291 -290 289 -290 325 -290 291 -290 291 -288 291 -290 289 -326 291 -290 287 -290 287 -288 307 -278 283 -306 319 -288 287 -290 287 -328 291 -290 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -326 287 -290 289 -288 287 -308 275 -286 305 -316 287 -288 291 -288 327 -292 289 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 287 -290 287 -290 307 -276 283 -304 319 -286 287 -290 327 -290 291 -290 291 -290 289 -290 325 -290 291 -290 291 -288 291 -288 289 -326 287 -290 287 -290 287 -288 307 -278 315 -274 319 -286 289 -288 291 -328 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -290 291 -292 289 -292 325 -290 289 -288 287 -288 307 -278 317 -274 317 -286 289 -290 327 -288 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 287 -290 287 -290 307 -276 317 -274 317 -288 287 -290 289 -328 291 -288 291 -288 291 -290 325 -290 289 -290 291 -288 291 -288 289 -324 289 -290 287 -290 287 -324 271 -278 317 -274 319 -288 289 -288 291 -326 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -292 325 -292 287 -290 287 -288 321 -272 277 -320 273 -318 287 -288 289 -598 571 -584 615 -576 577 -314 279 -310 291 -312 285 -584 283 -322 289 -290 571 -294 277 -322 309 -556 317 -288 577 -312 285 -288 307 -276 317 -272 319 -286 287 -290 325 -290 291 -288 291 -578 311 -286 289 -288 287 -308 275 -320 273 -320 583 -284 287 -324 289 -292 289 -292 289 -572 609 -598 281 -288 325 -292 291 -290 581 -278 321 -290 289 -562 615 -576 579 -314 287 -288 325 -288 289 -288 287 -594 291 -296 311 -286 287 -288 585 -316 285 -570 291 -312 293 -312 551 -318 285 -600 589 -578 599 -284 285 -588 313 -286 601 -290 279 -298 315 -590 577 -312 285 -564 291 -320 573 -318 287 -562 597 -310 269 -604 575 -588 313 -286 601 -290 279 -300 313 -590 285 -286 289 -328 541 -308 307 -97106 97 -298 201 -364 99 -98 99 -1556 97 -630 65 -796 65 -662 99 -330 927 -66 12751 -12286 165 -494 97 -396 65 -232 131 -432 197 -434 231 -792 197 -132 261 -132 131 -98 427 -68 165 -202 1759 -3988 65 -9068 199 -662 65 -998 199 -132 97 -1094 197 -268 99 -466 97 -100 265 -100 97 -134 20449 -12394 163 -1026 165 -956 133 -694 65 -398 661 -166 165 -100 431 -100 533 -100 2891 -12710
|
||||
RAW_Data: 199 -302 99 -2486 65 -262 65 -264 99 -98 165 -428 327 -330 65 -262 229 -134 195 -100 29481 -3916 295 -292 261 -8458 129 -100 131 -2822 65 -66 131 -428 163 -100 229 -66 163 -166 97 -66 197 -264 111819 -12216 99 -2338 99 -1320 165 -366 629 -68 67 -266 11581 -13114 167 -134 99 -900 101 -66 97 -466 165 -334 97 -700 331 -296 65 -232 165 -134 463 -166 959 -15662 131 -598 99 -888 65 -132 65 -424 99 -692 99 -868 233 -402 165 -404 495 -264 1225 -100 59983 -10602 133 -1484 99 -66 163 -864 99 -332 165 -466 197 -824 65 -66 165 -66 493 -166 263 -12072 165 -728 63 -928 65 -234 465 -464 297 -264 99 -168 165 -134 167 -66 299 -66 199 -98 601 -132 2391 -66 20605 -12118 63 -622 161 -98 165 -164 133 -432 165 -270 131 -364 131 -66 65 -864 65 -364 65 -198 231 -100 199 -298 623 -100 131 -132 461 -66 11413 -12018 65 -1318 133 -362 65 -166 165 -364 65 -1116 65 -626 197 -166 363 -200 2527 -12294 431 -564 131 -666 99 -332 65 -764 65 -334 65 -466 133 -102 459 -306 229 -376 243 -350 209 -386 209 -350 245 -350 237 -346 255 -338 243 -352 245 -354 245 -318 245 -352 247 -352 245 -306 281 -320 269 -314 281 -320 247 -356 249 -318 283 -320 247 -320 283 -320 269 -280 287 -340 279 -284 285 -322 285 -288 285 -324 285 -286 285 -322 249 -322 285 -320 269 -278 287 -342 281 -286 287 -324 289 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -290 289 -290 325 -288 289 -288 287 -276 307 -284 303 -318 285 -288 289 -290 327 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 287 -326 287 -290 287 -290 287 -308 275 -318 273 -318 287 -288 325 -288 291 -288 291 -290 289 -290 325 -290 291 -288 291 -288 291 -288 289 -326 287 -290 287 -290 287 -324 269 -278 317 -310 285 -288 287 -290 289 -328 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 289 -326 289 -288 289 -286 323 -272 277 -286 339 -282 287 -288 289 -290 325 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -290 323 -290 287 -290 287 -288 307 -278 283 -306 317 -288 287 -290 289 -328 289 -292 291 -290 291 -290 291 -290 325 -290 289 -290 291 -288 289 -288 325 -288 289 -290 287 -288 307 -276 283 -306 317 -286 289 -288 327 -292 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 291 -290 291 -290 291 -288 289 -326 287 -290 287 -288 307 -276
|
||||
RAW_Data: 285 -304 319 -284 289 -286 327 -290 289 -290 289 -290 289 -292 291 -326 291 -292 289 -292 289 -292 287 -290 325 -288 289 -288 289 -288 307 -276 283 -304 319 -286 287 -290 325 -292 291 -290 291 -290 291 -292 289 -292 289 -328 291 -288 291 -288 291 -288 325 -290 287 -290 287 -286 321 -272 277 -320 273 -318 287 -288 287 -328 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -292 289 -292 287 -326 289 -288 289 -288 289 -288 305 -278 317 -274 317 -288 287 -290 327 -288 291 -288 291 -288 291 -288 327 -290 289 -290 291 -290 291 -290 291 -324 289 -290 287 -290 287 -596 587 -578 577 -624 579 -292 295 -310 287 -288 287 -598 281 -324 287 -292 571 -310 269 -312 293 -578 317 -288 581 -310 285 -290 287 -278 307 -284 301 -318 287 -288 289 -290 327 -290 291 -548 597 -292 309 -294 313 -286 287 -290 287 -328 291 -290 291 -290 291 -292 289 -602 559 -610 565 -618 573 -588 291 -286 303 -318 287 -288 289 -290 587 -314 287 -288 289 -600 577 -304 315 -554 611 -280 321 -288 289 -562 617 -274 319 -552 317 -286 291 -328 289 -292 573 -582 293 -300 579 -318 289 -288 291 -290 325 -290 289 -572 579 -296 301 -316 285 -592 283 -288 575 -586 321 -294 575 -588 601 -308 271 -278 319 -310 285 -592 577 -312 285 -290 287 -288 323 -560 293 -300 315 -286 585 -282 323 -104040 133 -692 99 -532 65 -198 165 -100 197 -266 267 -100 67 -132 427 -296 65 -166 12121 -13754 65 -2966 295 -360 197 -364 163 -134 65 -68 297 -100 10257 -12380 131 -1262 97 -1362 163 -298 165 -130 201 -100 167 -132 199 -232 165 -134 927 -16600 165 -3156 165 -952 131 -558 261 -98 623 -164 853 -66 17629 -12770 131 -728 133 -2794 165 -336 65 -200 167 -100 329 -132 131 -66 1557 -68 3343 -13640 131 -134 165 -268 65 -1166 231 -234 99 -298 133 -168 67 -368 99 -98 161 -232 131 -232 74589 -5056 65 -8260 67 -564 97 -1332 97 -496 65 -264 893 -100 97 -264 499 -15178 97 -298 165 -400 197 -332 99 -502 163 -396 65 -330 99 -266 99 -330 161 -396 165 -100 133 -168 165 -100 429 -198 131 -100 131 -164 261 -64 1631 -100 42669 -6566 99 -2458 231 -198 265 -100 261 -98 259 -592 12505 -12296 65 -1250 163 -1376 131 -230 197 -234 133 -300 401 -164 163 -328 65 -98 395 -132 63 -98 163 -266 33537 -13670 165 -764 65 -466 65 -730 333 -262 261 -66 163 -66 395 -264 753 -66 1351 -64 163 -100 15801 -12262 229 -886 99 -362 131 -1844 97 -264 593 -66 563 -100 131 -100
|
||||
RAW_Data: 133 -264 79019 -12402 99 -602 263 -100 65 -132 433 -198 161 -230 627 -66 60423 -12626 163 -328 99 -426 65 -1120 929 -98 133 -100 297 -100 129 -68 2373 -230 15313 -284 327 -290 291 -292 289 -292 289 -292 289 -290 307 -274 317 -274 321 -288 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 325 -256 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -288 307 -276 315 -276 319 -290 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -290 323 -242 305 -284 303 -318 289 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 323 -254 305 -276 317 -276 321 -288 289 -290 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -288 325 -242 303 -286 341 -286 287 -290 289 -292 327 -254 327 -292 289 -292 289 -292 327 -252 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 289 -290 325 -272 273 -286 341 -286 289 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 325 -256 327 -254 327 -290 289 -288 287 -288 307 -276 317 -274 319 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -288 325 -272 275 -288 341 -286 289 -288 289 -290 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -288 307 -276 315 -274 321 -288 289 -290 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -290 323 -272 275 -318 309 -286 287 -292 289 -292 291 -290 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -256 325 -292 289 -290 287 -290 325 -272 275 -286 339 -286 289 -290 291 -290 291 -292 325 -564 603 -582 583 -580 579 -316 287 -292 325 -292 291 -562 289 -286 305 -320 585 -286 325 -252 327 -540 307 -308 595 -276 321 -288 289 -290 289 -292 327 -254 327 -292 289 -292 289 -566 313 -288 603 -578 301 -278 321 -286
|
||||
RAW_Data: 587 -578 311 -288 289 -288 325 -272 273 -318 575 -318 289 -292 289 -292 327 -254 327 -254 327 -292 289 -570 287 -308 291 -314 285 -290 587 -590 589 -578 303 -278 589 -318 273 -278 309 -578 317 -282 597 -578 305 -318 551 -320 287 -292 289 -604 589 -260 311 -296 317 -558 317 -290 289 -292 577 -588 613 -276 321 -288 287 -588 283 -288 327 -292 571 -580 587 -582 613 -590 581 -588 579 -578 595 -294 307 -294 313 -558 319 -288 291 -290 613 -274 323 -110418 63 -2752 65 -166 265 -364 263 -66 199 -68 299 -132 133 -100 233 -68 495 -100 1091 -66 231 -10708 131 -1196 65 -2188 131 -132 63 -164 163 -558 197 -134 401 -598 297 -132 497 -66 99 -66 665 -15590 165 -1460 65 -598 63 -1690 99 -662 165 -430 229 -98 63 -130 2315 -15112 65 -328 99 -500 65 -464 67 -1092 65 -232 67 -500 65 -830 363 -100 97 -166 861 -134 1717 -12880 65 -632 65 -956 165 -1746 329 -164 97 -854 165 -198 63 -66 1353 -100 1023 -132 12247 -12372 65 -526 129 -654 65 -1132 65 -198 165 -234 67 -788 295 -98 65 -398 99 -66 625 -66 63 -132 197 -12390 233 -266 65 -600 131 -562 99 -1690 329 -264 163 -228 459 -166 329 -66 131 -66 2015 -68 531 -12154 229 -760 133 -132 65 -132 163 -464 497 -66 163 -332 431 -166 461 -66 527 -66 361 -132 10399 -12108 163 -4366 565 -100 201 -66 167 -100 633 -66 559 -430 31103 -12642 65 -266 167 -264 133 -960 65 -756 131 -130 97 -820 393 -294 229 -100 499 -364 661 -200 429 -232 33603 -11996 99 -164 65 -856 65 -262 131 -1516 65 -600 99 -500 65 -100 265 -66 99 -432 763 -98 263 -134 165 -66 599 -164 1419 -12134 65 -896 67 -300 231 -168 233 -400 197 -66 431 -66 133 -166 199 -234 1431 -132 133 -14994 133 -266 131 -1710 65 -100 229 -662 133 -432 197 -66 723 -296 163 -98 99 -66 129 -132 99 -98 163 -626 1293 -166 35681 -12074 197 -134 165 -166 133 -802 131 -434 131 -1862 231 -166 129 -196 429 -234 435 -12364 65 -166 99 -722 163 -336 65 -68 129 -990 1131 -100 131 -366 299 -166 1509 -66 26029 -12314 197 -234 97 -1782 331 -130 197 -130 65 -164 193 -324 163 -98 131 -66 363 -266 3983 -13386 131 -268 99 -1220 99 -1462 97 -298 633 -298 231 -232 1821 -12714 231 -364 133 -166 99 -864 165 -594 163 -1182 197 -100 195 -66 589 -262 99 -296 65 -298 1673 -98 2839 -13778 97 -1552 131 -1824 133 -430 2963 -66 24183 -12728 99 -426 327 -98 67 -1184 99 -920 759 -66 195 -98 455 -460 1821 -254 327 -290 291 -290 291 -292
|
||||
RAW_Data: 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 315 -276 321 -288 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -288 325 -242 335 -258 341 -286 289 -288 291 -292 325 -256 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -288 307 -276 317 -274 321 -288 291 -288 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -288 325 -272 275 -316 309 -288 287 -292 291 -290 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 327 -256 325 -256 327 -290 291 -290 291 -292 289 -288 309 -274 315 -274 319 -290 287 -292 289 -292 327 -290 291 -292 289 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 287 -290 323 -242 307 -284 341 -284 289 -290 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 325 -240 305 -284 343 -286 289 -288 327 -252 327 -254 327 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -290 307 -274 317 -276 319 -290 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 289 -290 325 -252 309 -276 317 -276 319 -290 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 287 -292 323 -244 305 -284 301 -320 287 -290 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -570 609 -564 615 -576 587 -290 319 -274 319 -288 289 -584 283 -290 327 -290 573 -294 305 -292 309 -556 319 -288 581 -278 323 -290 291 -290 291 -288 307 -276 315 -274 321 -288 289 -588 319 -286 575 -274 307 -598 275 -322 585 -556 597 -578 627 -586 575 -312 287 -290 289 -278 305 -282 301 -588 613 -278 325 -564 577 -606 319 -250 327 -290 583 -590 309 -242 307 -284 605 -284 327 -290 291 -290 291 -292 289 -292
|
||||
RAW_Data: 327 -290 291 -570 285 -278 617 -282 325 -548 311 -280 599 -578 317 -290 291 -290 327 -254 327 -292 289 -292 291 -290 569 -294 307 -594 583 -570 343 -272 597 -576 321 -290 549 -590 613 -588 309 -288 585 -282 327 -252 327 -572 311 -270 275 -320 575 -320 289 -109438 459 -164 623 -2320 99 -502 99 -166 365 -164 165 -132 301 -66 2721 -3824 101 -468 99 -300 67 -1678 131 -1618 133 -100 365 -166 131 -264 561 -100 165 -168 1191 -66 133 -100 55637 -11990 65 -330 65 -2818 263 -402 131 -304 199 -132 163 -166 263 -134 359 -66 393 -100 755 -13000 99 -2872 65 -526 97 -330 97 -168 499 -364 167 -364 1155 -198 1791 -12214 65 -66 263 -266 99 -2088 65 -198 331 -100 231 -266 197 -100 895 -12188 333 -692 163 -656 197 -756 131 -1094 163 -136 763 -166 1291 -66 163 -12178 65 -1092 99 -1062 97 -664 131 -200 691 -264 63 -164 229 -162 65 -132 129 -66 1743 -66 4921 -10270 67 -2254 65 -364 99 -462 65 -426 65 -528 97 -1180 493 -100 197 -200 131 -102 231 -66 299 -100 95335 -12362 99 -1314 129 -362 99 -522 131 -132 199 -498 131 -400 65 -334 333 -132 99 -68 197 -266 397 -362 99 -198 56451 -12022 99 -66 63 -1262 97 -1234 65 -266 67 -530 133 -498 531 -66 633 -66 2275 -9896 67 -298 99 -264 67 -1122 99 -196 229 -230 295 -694 67 -562 1019 -166 199 -464 99 -100 1759 -66 11953 -340 217 -346 251 -350 267 -312 253 -344 253 -314 253 -366 241 -352 247 -312 251 -344 259 -340 245 -318 279 -316 279 -320 269 -316 251 -310 281 -312 285 -304 317 -280 277 -314 279 -318 273 -320 279 -278 311 -278 317 -274 319 -282 275 -312 277 -320 275 -320 281 -276 311 -278 319 -274 319 -282 275 -312 277 -320 273 -320 281 -276 311 -278 319 -276 319 -282 275 -312 279 -320 309 -282 279 -276 311 -280 317 -276 319 -280 275 -312 279 -318 275 -320 281 -276 311 -278 319 -310 285 -280 275 -312 277 -320 273 -322 279 -278 311 -278 319 -274 319 -280 277 -312 277 -320 307 -286 279 -278 311 -280 319 -276 319 -282 275 -312 279 -318 273 -318 281 -276 311 -278 319 -274 319 -282 275 -314 277 -318 273 -320 281 -276 311 -278 319 -276 319 -280 275 -312 279 -318 275 -320 279 -276 311 -280 319 -274 319 -280 277 -312 277 -322 309 -284 277 -278 311 -280 321 -274 319 -282 275 -312 279 -318 273 -318 281 -276 311 -278 319 -274 319 -282 275 -312 277 -320 275 -320 279 -276 311 -278 321 -274 319 -280 277 -312 277 -320 273 -320 279 -278 311 -278 319 -274 319 -282
|
||||
RAW_Data: 275 -314 277 -322 309 -282 279 -278 311 -280 319 -274 319 -280 275 -312 279 -318 275 -320 279 -276 313 -278 317 -276 319 -280 275 -312 279 -320 273 -320 279 -278 311 -278 317 -310 285 -280 275 -312 279 -320 273 -320 281 -276 311 -278 319 -274 319 -282 307 -282 277 -322 273 -318 281 -278 309 -280 319 -274 319 -282 275 -314 277 -318 271 -320 281 -276 313 -278 317 -276 319 -280 275 -312 279 -320 273 -320 279 -278 311 -280 317 -276 317 -282 275 -312 277 -320 273 -320 281 -276 311 -280 321 -274 317 -282 275 -314 277 -320 275 -318 279 -278 311 -280 319 -274 317 -280 277 -312 277 -320 273 -320 279 -278 311 -278 317 -310 285 -280 275 -312 279 -320 273 -320 281 -276 311 -278 319 -274 319 -282 275 -312 277 -320 309 -284 279 -278 311 -278 321 -274 321 -282 273 -314 279 -318 273 -318 281 -276 311 -278 319 -310 283 -282 275 -312 277 -320 275 -320 279 -278 309 -280 317 -310 285 -574 571 -616 575 -586 601 -310 271 -278 321 -310 285 -590 283 -288 289 -326 573 -274 305 -278 321 -576 317 -290 579 -278 321 -288 289 -288 287 -308 275 -320 271 -320 285 -290 287 -598 571 -586 323 -294 575 -578 597 -608 263 -306 317 -286 585 -588 589 -292 283 -300 315 -284 289 -288 325 -290 291 -548 595 -294 309 -294 313 -554 319 -276 309 -280 579 -316 285 -326 289 -550 597 -294 307 -592 579 -318 287 -570 579 -296 303 -582 317 -288 287 -292 291 -326 291 -292 573 -582 293 -300 579 -318 287 -290 289 -292 289 -604 275 -322 289 -288 287 -278 307 -286 601 -578 605 -580 599 -586 281 -324 571 -308 271 -278 323 -310 285 -286 289 -288 327 -546 313 -286 325 -290 561 -290 289 -101844 97 -1446 131 -798 99 -3708 99 -100 99 -200 65 -298 101 -234 163 -66 233 -98 99 -66 365 -234 97 -134 231 -66 11807 -13182 99 -196 131 -260 129 -952 97 -428 65 -756 65 -66 359 -330 163 -232 295 -194 31123 -12262 163 -852 99 -298 197 -262 65 -392 197 -1334 99 -830 497 -232 397 -298 131 -66 99 -132 26673 -13054 65 -66 131 -1892 65 -402 65 -266 563 -198 67 -98 565 -66 131 -198 65 -100 65 -100 625 -98 14851 -12546 97 -2024 99 -1392 595 -66 99 -198 199 -100 131 -66 12339 -12596 297 -1524 99 -1784 129 -66 195 -98 757 -164 165 -100 99 -100 231 -200 429 -100 61537 -12596 227 -426 97 -560 261 -1936 199 -66 597 -230 567 -66 131 -100 65 -266 261 -66 391 -15266 67 -334 165 -630 165 -68 65 -532 65 -994 327 -492 97 -166 265 -362 1359 -132
|
||||
RAW_Data: 631 -98 329 -100 159267 -12116 99 -1028 165 -1616 131 -588 689 -230 231 -294 985 -100 22253 -12324 65 -3752 99 -132 267 -66 65 -66 533 -100 499 -132 365 -132 531 -132 10687 -12868 65 -2788 163 -498 167 -432 399 -100 197 -426 65 -232 817 -15572 65 -132 163 -3584 665 -164 495 -68 131 -100 97 -100 65 -98 163 -66 657 -100 1015 -12608 131 -632 65 -396 99 -134 265 -332 65 -100 131 -264 65 -266 199 -896 663 -332 265 -100 329 -68 1645 -132 36927 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 289 -290 323 -242 307 -282 337 -284 287 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 325 -272 273 -288 343 -284 291 -288 289 -290 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -288 307 -276 315 -274 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 325 -256 327 -254 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 289 -290 325 -272 273 -318 309 -286 289 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 327 -254 327 -254 327 -290 289 -292 287 -290 323 -272 275 -286 339 -286 289 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -290 323 -242 307 -284 341 -284 287 -288 325 -254 325 -256 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -290 323 -272 275 -286 341 -286 287 -290 289 -292 327 -254 327 -290 291 -292 289 -290 327 -254 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 289 -290 325 -288 271 -278 317 -276 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 287 -290 323 -242 305 -284 307 -320 289 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -288 325 -242 305 -286 337 -284 289 -288 291 -290 325 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292
|
||||
RAW_Data: 289 -292 327 -290 291 -292 289 -292 289 -292 287 -290 307 -562 609 -588 603 -582 585 -312 289 -288 289 -290 325 -564 317 -290 289 -292 569 -286 309 -292 313 -556 319 -288 579 -312 287 -292 287 -290 307 -274 317 -276 319 -290 287 -292 289 -598 283 -288 327 -292 289 -292 571 -580 593 -582 319 -288 291 -290 611 -584 581 -578 613 -280 289 -292 325 -292 291 -562 611 -274 319 -288 287 -590 319 -250 611 -274 307 -564 307 -320 287 -290 585 -590 313 -288 563 -292 321 -272 319 -558 613 -558 609 -302 313 -550 317 -290 291 -290 291 -290 577 -618 583 -576 319 -288 581 -276 323 -292 291 -290 287 -290 307 -276 315 -276 319 -558 319 -290 579 -310 287 -290 323 -560 587 -312 287 -288 325 -254 325 -292 291 -546 313 -278 309 -296 581 -318 289 -109184 99 -1852 131 -1658 201 -400 629 -694 265 -134 233 -66 8565 -12286 199 -398 97 -500 131 -1682 131 -1026 299 -300 165 -332 329 -368 329 -134 1129 -16010 65 -328 65 -1022 131 -820 99 -832 131 -232 99 -500 131 -498 129 -166 199 -132 99 -100 795 -15326 97 -426 65 -3764 133 -798 97 -134 99 -168 165 -168 831 -132 65 -198 555 -164 16039 -13964 97 -332 97 -364 131 -98 197 -200 165 -166 167 -66 165 -100 99 -198 167 -400 661 -66 201 -134 199 -12232 65 -1260 99 -1228 65 -232 133 -766 65 -366 299 -462 65 -262 525 -232 131 -132 461 -16550 65 -134 99 -696 165 -794 131 -232 165 -162 97 -164 197 -788 165 -64 727 -132 923 -66 22915 -13026 65 -230 393 -398 99 -1094 399 -332 63 -298 757 -64 391 -132 533 -15112 99 -234 199 -730 67 -332 99 -464 395 -430 163 -362 131 -262 163 -366 16087 -12630 65 -500 131 -794 329 -98 263 -230 391 -196 427 -102 167 -102 431 -132 467 -132 16491 -11990 67 -862 65 -690 97 -1812 97 -496 99 -168 133 -98 267 -102 2557 -11336 65 -1830 131 -700 267 -364 65 -1030 65 -230 99 -332 195 -130 687 -66 399 -396 231 -98 1323 -66 34643 -14182 65 -100 167 -1428 133 -894 199 -298 1427 -10950 165 -1026 133 -1162 67 -332 133 -1324 99 -434 65 -200 133 -66 429 -132 199 -362 1195 -12456 197 -962 99 -466 99 -832 65 -854 129 -662 231 -132 397 -262 65 -262 231 -64 131 -66 593 -98 1387 -15870 99 -934 131 -166 397 -66 231 -300 197 -266 525 -200 429 -100 531 -66 165 -68 263 -66 35093 -246 305 -280 321 -278 321 -290 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 289 -290
|
||||
RAW_Data: 289 -288 307 -276 315 -274 321 -288 291 -290 291 -292 325 -256 327 -290 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 287 -290 305 -276 317 -274 319 -288 289 -290 325 -256 327 -290 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 325 -240 305 -284 341 -286 289 -290 291 -290 327 -254 327 -292 289 -292 291 -288 327 -254 327 -256 325 -292 291 -290 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -290 307 -274 317 -274 319 -290 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 291 -290 287 -290 325 -272 273 -318 309 -288 287 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 287 -290 323 -242 307 -284 341 -286 289 -288 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -288 307 -276 315 -274 319 -290 289 -292 325 -256 327 -254 327 -288 291 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -292 291 -290 325 -252 307 -276 319 -274 321 -290 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -290 289 -288 307 -276 317 -276 321 -288 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -290 289 -288 323 -242 307 -284 339 -284 291 -288 291 -290 291 -292 325 -292 289 -292 291 -290 291 -290 291 -602 591 -576 599 -586 573 -312 287 -290 323 -290 271 -598 311 -286 287 -290 585 -282 327 -292 289 -574 307 -272 599 -278 321 -288 291 -290 291 -292 325 -292 291 -290 291 -290 291 -566 597 -292 309 -588 319 -290 291 -288 291 -290 291 -290 579 -586 289 -322 575 -320 291 -290 291 -566 599 -292 309 -590 581 -572 619 -258 323 -578 579 -318 287 -570 291 -310 293 -314 287 -288 587 -284 323 -574 579 -294 299 -278 321 -560 319 -288 291 -292 325 -256 327 -290 573 -580 589 -314 287 -594 285 -288 579 -584 611 -276 321 -560 317 -290 291 -290 577 -584 615 -578
|
||||
RAW_Data: 581 -316 287 -290 291 -602 287 -282 299 -318 585 -286 289 -292 325 -256 327 -574 309 -272 275 -318 575 -318 291 -7980 317 -276 317 -274 319 -280 275 -310 277 -320 309 -288 279 -274 311 -276 321 -276 321 -278 275 -312 307 -290 309 -288 279 -274 311 -278 319 -278 321 -280 273 -312 277 -320 309 -288 279 -276 309 -278 321 -276 313 -276 309 -280 317 -272 321 -250 303 -312 277 -320 309 -286 279 -274 311 -278 319 -274 321 -280 275 -310 277 -322 309 -286 279 -276 309 -278 321 -276 321 -280 273 -312 277 -320 311 -286 279 -276 311 -278 319 -276 323 -250 335 -282 307 -290 311 -284 277 -278 309 -278 321 -274 321 -250 305 -310 277 -320 309 -286 279 -276 311 -276 319 -276 321 -280 273 -312 307 -292 311 -288 277 -276 311 -276 321 -276 321 -278 275 -312 277 -318 311 -286 279 -276 309 -280 321 -274 323 -250 335 -282 307 -292 309 -284 279 -276 309 -278 319 -276 321 -280 273 -312 277 -318 311 -286 279 -276 309 -278 321 -276 321 -280 273 -312 277 -320 311 -286 279 -276 309 -278 319 -276 321 -280 275 -310 277 -322 309 -286 279 -276 311 -278 323 -274 323 -250 303 -314 307 -290 309 -286 277 -276 311 -278 317 -276 321 -280 273 -312 277 -320 309 -288 279 -274 311 -278 319 -276 321 -280 273 -312 277 -320 309 -288 279 -274 311 -278 319 -278 321 -280 273 -312 307 -290 311 -286 277 -278 309 -280 321 -276 321 -252 303 -312 277 -288 343 -284 279 -274 311 -278 319 -274 321 -280 275 -310 277 -320 311 -286 279 -276 309 -278 319 -276 321 -280 273 -312 277 -320 311 -286 279 -276 309 -278 319 -276 321 -280 307 -280 309 -290 309 -286 277 -278 309 -280 321 -276 321 -252 303 -312 277 -318 309 -286 279 -276 309 -278 319 -276 321 -280 273 -312 277 -320 309 -288 279 -274 311 -278 319 -276 321 -280 275 -310 277 -322 309 -286 279 -276 311 -278 319 -276 321 -282 305 -282 307 -290 311 -284 279 -276 309 -280 319 -276 321 -250 303 -312 277 -318 311 -286 279 -274 311 -278 319 -276 321 -280 273 -312 275 -322 309 -288 279 -274 311 -278 319 -278 321 -280 275 -310 277 -320 309 -288 279 -274 311 -278 321 -276 321 -280 305 -282 307 -292 311 -284 279 -276 311 -278 319 -576 577 -600 607 -562 613 -286 287 -292 327 -290 291 -572 309 -272 311 -292 575 -320 289 -290 291 -582 313 -288 561 -292 319 -274 321 -286 291 -288 327 -254 325 -292 289 -292 291 -580 591 -288 283 -600 317 -292 291 -290 291 -290 291 -292 601 -558 319 -282
|
||||
RAW_Data: 597 -284 325 -290 291 -564 597 -286 309 -588 581 -590 587 -290 317 -574 581 -316 287 -570 291 -308 295 -314 287 -288 587 -284 323 -568 587 -290 283 -302 317 -558 319 -286 327 -254 325 -292 291 -290 571 -580 589 -316 285 -596 285 -290 609 -584 581 -276 323 -560 319 -288 291 -290 611 -552 613 -578 581 -316 287 -292 289 -602 287 -284 299 -316 587 -286 287 -292 327 -254 327 -568 309 -290 287 -292 593 -260 325 -106694 197 -260 293 -64 97 -3960 131 -760 65 -1322 65 -132 297 -198 97 -756 131 -132 97 -230 1281 -66 57401 -13630 231 -824 793 -264 463 -66 70681 -16390 97 -232 163 -132 97 -462 229 -230 229 -198 461 -360 755 -66 6885 -11986 99 -368 65 -896 229 -130 63 -132 229 -264 99 -230 97 -100 65 -98 197 -164 5469 -12452 65 -692 99 -298 131 -198 129 -756 97 -1182 97 -100 99 -98 329 -262 459 -264 6441 -12134 99 -430 199 -400 131 -132 65 -332 65 -1590 199 -832 495 -98 331 -166 531 -166 199 -130 1353 -132 63579 -12592 99 -664 199 -98 131 -496 231 -422 99 -1350 299 -266 363 -134 231 -166 199 -300 67 -100 12257 -6784 65 -366 65 -200 99 -2088 65 -798 231 -66 665 -198 665 -66 425 -66 27025 -12492 231 -828 65 -132 99 -958 199 -600 99 -430 197 -200 263 -300 131 -166 163 -164 229 -66 1417 -430 69 -506 63 -512 95 -510 65 -564 63 -446 191 -412 161 -432 171 -420 157 -414 191 -416 165 -406 207 -352 221 -380 221 -388 201 -378 207 -384 209 -384 209 -348 243 -350 237 -346 257 -338 243 -348 245 -352 243 -352 209 -350 245 -352 239 -346 255 -338 243 -352 245 -354 247 -318 281 -318 247 -320 283 -318 269 -312 259 -340 245 -354 247 -320 285 -322 251 -322 287 -286 285 -322 285 -286 285 -306 277 -318 273 -316 285 -286 325 -290 289 -290 291 -290 291 -290 291 -292 325 -292 289 -292 291 -290 291 -290 291 -292 325 -292 289 -290 287 -290 287 -326 271 -276 317 -276 317 -288 287 -292 291 -326 291 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 289 -290 291 -288 325 -288 289 -288 289 -288 287 -308 275 -318 273 -320 287 -288 289 -290 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 289 -290 287 -290 323 -274 275 -286 307 -318 287 -288 327 -252 325 -290 289 -290 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -292 325 -292 289 -290 287 -290 287 -324 271 -278 317 -276 319 -286 289 -288 291 -328 289 -292 291 -290
|
||||
RAW_Data: 291 -290 291 -292 325 -290 289 -290 289 -290 291 -288 325 -288 289 -288 287 -290 287 -308 275 -318 273 -320 287 -288 289 -290 327 -292 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 291 -290 291 -290 291 -292 289 -326 289 -290 287 -290 287 -288 307 -276 317 -274 319 -288 289 -288 327 -288 291 -288 291 -288 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -292 323 -290 289 -288 289 -288 307 -276 285 -306 317 -288 287 -288 291 -328 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 289 -290 291 -288 291 -288 325 -288 289 -288 287 -278 307 -284 301 -318 285 -288 289 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 323 -290 287 -290 287 -290 287 -306 277 -318 273 -320 285 -288 327 -290 289 -290 289 -290 289 -290 291 -324 291 -292 289 -572 581 -590 579 -622 561 -292 309 -294 311 -286 287 -594 281 -324 289 -290 571 -308 271 -312 291 -580 317 -274 599 -292 293 -312 285 -282 275 -314 277 -322 309 -282 279 -278 311 -600 275 -318 577 -590 577 -604 577 -572 605 -588 311 -282 275 -312 277 -322 309 -284 575 -302 275 -310 291 -578 317 -276 277 -310 609 -580 283 -324 287 -292 291 -290 571 -292 311 -292 311 -552 609 -314 285 -290 287 -596 583 -312 283 -592 575 -312 285 -290 287 -288 307 -278 317 -574 577 -316 285 -290 325 -288 289 -288 289 -596 583 -580 611 -280 321 -288 289 -564 581 -606 579 -588 309 -286 595 -584 577 -318 287 -292 291 -290 291 -328 289 -292 289 -572 285 -310 293 -312 555 -318 287 -102572 65 -1084 131 -1618 65 -328 65 -368 131 -1196 133 -364 493 -230 361 -132 295 -66 1823 -66 35725 -12798 65 -198 65 -428 131 -230 63 -360 65 -426 99 -730 131 -628 133 -468 597 -298 19945 -12560 99 -498 165 -464 67 -266 97 -2294 163 -100 65 -164 99 -134 99 -266 99 -666 165 -202 4325 -13250 65 -302 67 -366 65 -502 231 -496 327 -66 131 -300 463 -230 129 -100 491 -98 559 -15398 131 -266 167 -368 165 -1330 199 -662 131 -596 331 -66 231 -100 65 -166 99 -66 99 -66 133 -132 233 -134 27601 -11880 65 -720 97 -436 133 -168 231 -68 199 -132 99 -166 65 -396 925 -68 535 -100 859 -10684 65 -3682 99 -1986 233 -132 363 -264 631 -166 265 -68 10543 -11464 197 -332 131 -232 131 -1418 97 -1780 65 -68 165 -428 165 -100 199 -266 629 -19236 99 -862 97 -66 65 -886 63 -922 65 -656 65 -296 327 -66 229 -64 295 -262 163 -430 65 -234
|
||||
RAW_Data: 163 -202 893 -132 197 -66 197 -64 73201 -12342 65 -230 229 -788 65 -1550 65 -230 131 -856 427 -66 197 -294 165 -134 2359 -66 70273 -11456 99 -526 165 -166 65 -234 99 -100 265 -200 99 -432 65 -400 65 -366 131 -364 131 -68 197 -198 165 -366 65 -500 699 -66 64755 -10728 65 -626 165 -660 65 -724 97 -464 65 -232 101 -662 331 -66 231 -100 65 -100 65 -266 231 -66 65 -164 265 -198 591 -100 16555 -16474 99 -196 199 -332 97 -696 65 -266 65 -300 133 -98 699 -134 97 -100 26145 -12264 131 -430 65 -132 197 -2224 199 -832 233 -98 633 -132 163 -230 197 -364 36001 -17606 197 -294 195 -296 887 -66 65 -166 787 -12140 131 -98 133 -566 165 -566 65 -1360 131 -498 97 -560 631 -166 163 -100 197 -462 665 -100 1365 -15604 199 -198 67 -296 131 -786 97 -396 231 -298 99 -198 199 -100 99 -336 231 -100 817 -98 35409 -286 289 -292 289 -292 289 -290 307 -276 315 -274 319 -290 287 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 287 -290 289 -288 307 -276 317 -274 321 -288 291 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 289 -288 323 -242 307 -286 341 -282 289 -288 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -290 289 -288 307 -276 317 -274 319 -290 287 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 289 -290 289 -288 307 -276 317 -274 321 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 327 -256 325 -256 327 -290 291 -290 289 -290 289 -288 307 -276 317 -274 319 -290 287 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 327 -256 325 -292 289 -292 291 -290 325 -242 305 -286 301 -318 287 -288 327 -254 325 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -290 287 -278 305 -284 307 -320 289 -288 291 -290 327 -254 327 -292 289 -292 291 -288 327 -254 327 -256 325 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -288 325 -272 275 -284 305 -322 287 -290
|
||||
RAW_Data: 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 289 -288 325 -272 273 -286 341 -286 287 -290 289 -292 327 -254 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 287 -290 323 -272 277 -286 341 -284 289 -290 289 -290 325 -256 327 -290 291 -290 291 -290 291 -292 289 -292 327 -574 581 -584 579 -610 571 -290 319 -274 319 -288 289 -596 283 -290 327 -254 611 -274 305 -276 319 -574 319 -288 581 -278 323 -288 289 -288 323 -242 305 -284 341 -286 287 -290 289 -292 327 -254 327 -562 315 -288 291 -290 327 -254 601 -260 309 -598 317 -290 289 -292 579 -590 577 -606 283 -288 619 -562 611 -564 313 -288 595 -594 557 -292 309 -296 315 -288 287 -598 575 -592 287 -286 599 -286 323 -562 601 -310 271 -598 311 -286 287 -292 587 -284 323 -292 291 -572 579 -592 611 -556 313 -288 327 -256 325 -292 291 -290 563 -612 275 -320 289 -288 291 -290 327 -256 585 -590 309 -286 323 -274 595 -578 579 -318 289 -292 289 -292 289 -602 289 -280 299 -318 583 -284 327 -109884 199 -4348 527 -232 131 -632 63 -166 657 -98 65 -232 36025 -9982 65 -198 97 -1448 65 -864 165 -132 297 -166 65 -132 501 -100 431 -66 12953 -12456 329 -532 99 -364 327 -68 263 -300 429 -202 131 -134 463 -12156 131 -888 65 -1194 67 -562 65 -198 691 -296 63 -296 1621 -16532 133 -1894 65 -828 197 -266 199 -100 431 -66 165 -98 267 -100 65 -98 5745 -12464 65 -466 197 -1290 65 -692 97 -66 97 -66 165 -100 99 -102 99 -234 97 -600 11409 -14854 65 -1588 65 -66 195 -328 97 -230 99 -196 97 -98 129 -228 987 -16022 197 -1490 333 -1058 99 -98 4823 -10474 65 -3212 99 -928 263 -596 65 -202 133 -166 133 -134 527 -68 165 -134 67 -102 1545 -15786 99 -832 131 -1228 463 -364 65 -100 695 -100 51169 -13244 97 -3462 165 -466 133 -430 65 -464 99 -166 299 -100 131 -100 497 -66 1593 -16368 165 -262 261 -426 131 -266 65 -362 229 -98 393 -196 433 -134 429 -100 427 -132 43485 -13348 197 -394 65 -820 65 -66 99 -332 101 -334 99 -528 595 -362 197 -98 819 -66 131 -200 5627 -12542 67 -4232 131 -364 131 -132 363 -694 297 -66 263 -66 129 -66 165 -98 461 -328 67 -132 20457 -12390 163 -750 65 -232 167 -2154 197 -100 197 -234 429 -166 269 -300 297 -132 569 -12382 65 -64 165 -296 131 -1724 65 -1592 267 -234 197 -266 627 -134
|
||||
RAW_Data: 299 -134 1789 -98 70489 -12050 165 -264 65 -1066 97 -2154 97 -462 131 -66 131 -164 195 -100 63 -198 97 -164 163 -232 1387 -132 69781 -10702 65 -1390 165 -594 99 -3506 197 -270 331 -98 199 -200 201 -266 165 -166 4377 -12760 163 -2136 131 -892 65 -394 163 -196 131 -96 293 -396 97 -166 67 -66 99 -130 197 -98 2169 -132 39467 -12998 65 -364 65 -764 99 -132 131 -294 195 -792 165 -100 565 -300 197 -134 99 -232 99 -100 1263 -132 30791 -12756 165 -460 165 -432 131 -1068 65 -66 65 -496 65 -200 133 -100 397 -232 657 -298 229 -164 263 -66 129 -66 26055 -248 327 -290 291 -290 291 -290 325 -242 305 -284 341 -284 291 -288 289 -290 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -288 307 -276 315 -274 321 -288 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 325 -256 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 289 -290 307 -276 315 -276 321 -288 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 327 -256 325 -256 327 -290 291 -290 289 -290 289 -288 325 -272 275 -318 309 -286 289 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -290 323 -242 305 -286 339 -286 289 -288 289 -290 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -288 307 -276 315 -274 321 -288 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -290 323 -272 275 -284 341 -288 287 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 291 -290 289 -290 325 -272 273 -318 309 -288 287 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 323 -254 321 -274 277 -286 341 -284 289 -288 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -290 323 -272 275 -318 309 -286 289 -290 289 -292 325 -256 327 -290
|
||||
RAW_Data: 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -290 291 -292 289 -292 289 -292 287 -290 307 -276 315 -276 321 -288 291 -290 291 -586 605 -582 583 -580 613 -278 325 -254 327 -290 291 -564 291 -286 305 -322 557 -322 287 -292 289 -580 309 -290 591 -292 295 -278 323 -288 291 -290 291 -290 327 -256 325 -292 291 -564 599 -310 271 -600 311 -288 289 -290 587 -572 307 -290 307 -274 611 -282 325 -550 601 -578 589 -314 287 -290 291 -290 327 -292 289 -292 289 -292 291 -566 313 -288 327 -254 327 -292 289 -292 291 -290 597 -582 309 -288 585 -284 327 -254 327 -292 289 -292 289 -572 611 -564 613 -580 591 -578 599 -588 573 -312 287 -594 581 -314 285 -554 317 -286 611 -276 323 -272 275 -318 309 -556 319 -290 291 -292 289 -292 611 -552 321 -292 577 -318 289 -292 289 -292 291 -602 277 -290 325 -292 563 -290 283 -110406 97 -1498 99 -798 65 -498 99 -466 197 -100 531 -296 729 -100 39553 -10444 65 -886 265 -166 365 -396 99 -364 97 -590 199 -400 299 -496 165 -164 129 -132 659 -12808 67 -462 65 -630 65 -524 99 -328 97 -794 133 -66 229 -298 759 -98 1941 -12514 65 -830 99 -496 65 -362 65 -326 131 -796 165 -400 229 -198 165 -68 363 -398 97 -66 799 -66 35887 -11776 99 -534 99 -1026 263 -232 133 -698 655 -100 197 -100 133 -300 493 -166 1747 -100 15627 -12042 97 -958 63 -460 229 -994 231 -364 97 -164 97 -98 95 -232 65 -294 131 -132 427 -66 393 -11274 65 -664 131 -3220 331 -66 429 -300 99 -100 99 -100 13237 -12710 65 -928 167 -2086 65 -394 263 -294 97 -100 261 -462 197 -266 131 -200 1057 -66 31329 -10160 133 -1624 97 -400 67 -562 531 -230 429 -298 99 -164 195 -130 197 -11630 129 -132 63 -528 65 -566 131 -264 99 -428 63 -264 97 -330 165 -1002 99 -1610 359 -100 97 -232 395 -198 133 -336 263 -234 99 -100 297 -100 499 -66 563 -166 11665 -12516 295 -1294 99 -134 201 -1874 263 -264 65 -164 65 -264 97 -132 393 -100 2309 -15222 197 -460 129 -262 63 -558 427 -228 393 -164 559 -98 129 -132 65 -66 2907 -12208 129 -228 97 -592 163 -130 99 -564 199 -98 165 -200 699 -66 5371 -12298 229 -198 65 -1186 65 -898 65 -922 195 -196 131 -332 797 -12776 129 -1094 67 -232 163 -66 163 -98 165 -164 261 -164 327 -198 359 -100 2315 -132 1015 -11318 99 -1494 99 -1748 95 -856 195 -590 129 -296 195 -132 229 -198 4515 -12584 97 -700 97 -232 131 -134 231 -398 131 -864 99 -268 131 -398
|
||||
RAW_Data: 327 -428 97 -266 401 -134 397 -100 893 -100 41773 -12392 167 -134 1227 -232 197 -266 825 -66 527 -66 106369 -12550 99 -2282 385 -324 203 -382 219 -348 253 -348 223 -366 241 -352 219 -350 251 -350 233 -378 209 -350 251 -348 221 -354 269 -348 217 -344 253 -314 285 -334 243 -318 281 -316 251 -350 267 -312 251 -342 251 -312 287 -336 243 -318 279 -316 279 -320 269 -316 251 -308 283 -312 285 -304 317 -280 277 -314 277 -320 271 -318 281 -278 311 -280 319 -308 283 -282 277 -314 279 -318 273 -316 279 -278 311 -280 287 -340 283 -282 277 -312 279 -320 271 -318 281 -278 313 -278 287 -340 283 -280 277 -314 279 -318 273 -318 281 -276 313 -280 285 -340 283 -280 277 -314 277 -322 273 -318 279 -278 311 -280 321 -274 317 -282 277 -314 277 -320 271 -318 281 -276 313 -278 285 -340 283 -280 277 -314 277 -322 273 -318 279 -278 313 -278 287 -340 283 -282 275 -312 279 -320 275 -320 279 -276 311 -278 319 -274 319 -282 275 -314 277 -320 309 -284 277 -278 311 -280 319 -274 321 -280 275 -312 277 -320 273 -320 281 -276 311 -278 319 -274 319 -282 275 -312 277 -320 273 -320 279 -278 311 -278 319 -276 319 -280 277 -310 279 -318 275 -320 279 -276 311 -280 317 -276 321 -280 275 -312 279 -320 275 -318 279 -278 311 -278 319 -276 319 -280 275 -312 279 -318 273 -322 279 -276 313 -278 317 -276 319 -280 275 -312 277 -320 273 -320 281 -276 311 -278 319 -276 319 -280 277 -312 277 -320 273 -320 281 -276 311 -280 319 -274 319 -280 275 -314 277 -322 309 -282 279 -276 311 -280 319 -274 319 -280 277 -312 277 -320 273 -320 279 -278 311 -278 319 -274 319 -282 275 -312 277 -320 273 -320 281 -276 311 -278 319 -276 319 -282 275 -312 277 -320 273 -320 279 -278 311 -278 321 -274 321 -280 275 -314 277 -318 309 -284 279 -276 311 -278 319 -276 319 -282 275 -312 277 -320 273 -320 281 -276 311 -278 319 -274 319 -282 275 -312 277 -320 273 -320 281 -276 313 -278 319 -274 319 -282 275 -312 277 -320 309 -284 279 -278 311 -278 319 -276 319 -282 275 -314 277 -318 275 -318 281 -276 311 -278 319 -274 319 -280 277 -312 277 -320 273 -322 279 -278 311 -278 319 -274 319 -280 277 -312 277 -616 581 -570 607 -600 581 -278 311 -280 315 -272 319 -574 283 -310 279 -298 581 -318 273 -310 279 -612 281 -288 585 -316 287 -288 289 -326 287 -290 287 -290 287 -324 271 -278 317 -274 319 -288 289 -288 291 -586 589 -586 613 -578 317 -290 289 -292 581 -588
|
||||
RAW_Data: 579 -604 579 -318 289 -290 291 -572 293 -278 321 -310 285 -288 289 -288 587 -316 285 -290 287 -326 289 -290 289 -288 289 -288 307 -598 273 -318 287 -288 585 -318 285 -290 291 -288 291 -288 325 -288 289 -288 287 -278 307 -284 303 -586 317 -288 291 -292 289 -292 577 -618 583 -310 285 -590 577 -312 287 -562 613 -576 579 -590 583 -614 275 -320 285 -290 583 -602 273 -306 599 -274 319 -286 289 -594 281 -324 289 -290 571 -308 271 -103550 131 -132 131 -132 657 -134 755 -332 131 -396 97 -562 133 -592 361 -364 165 -66 131 -266 429 -64 165 -66 821 -132 1091 -66 13045 -13202 99 -656 165 -800 97 -760 99 -362 97 -132 165 -394 297 -166 99 -100 595 -166 229 -64 817 -100 197 -102 397 -232 131 -66 15701 -11694 131 -636 65 -132 861 -100 197 -68 331 -168 197 -134 397 -12314 131 -264 97 -796 167 -266 527 -100 131 -100 493 -100 65 -64 229 -132 1807 -98 50717 -12008 167 -336 65 -2216 97 -296 65 -658 197 -166 467 -298 99 -498 363 -98 163 -98 1741 -13254 65 -2196 65 -2392 65 -334 99 -566 65 -298 65 -166 131 -132 495 -66 133 -98 235 -12136 99 -796 199 -330 99 -266 131 -536 97 -864 165 -466 131 -232 163 -260 65 -66 163 -262 1023 -66 527 -166 1289 -12206 99 -232 131 -3820 131 -100 197 -98 161 -98 327 -196 227 -132 163 -362 825 -166 763 -12710 133 -828 131 -334 65 -264 65 -832 659 -132 1389 -16216 99 -696 231 -264 133 -166 97 -298 429 -68 295 -100 30351 -11382 65 -1556 199 -858 63 -2178 925 -132 329 -66 199 -396 327 -130 229 -64 227 -66 459 -15982 131 -898 63 -130 165 -694 99 -426 199 -164 297 -96 425 -98 131 -98 1217 -66 42981 -12724 97 -368 197 -66 65 -164 65 -164 129 -2176 265 -166 99 -166 397 -168 197 -66 689 -98 99 -100 2331 -12338 165 -234 131 -596 97 -66 65 -168 99 -466 197 -132 297 -398 167 -434 165 -298 265 -200 229 -66 199 -200 15425 -11384 99 -1586 165 -1528 99 -1726 133 -98 161 -196 163 -460 99 -496 163 -132 721 -100 31097 -4434 97 -264 131 -166 133 -594 263 -396 299 -430 165 -198 97 -164 761 -134 4579 -12250 163 -560 163 -198 163 -628 165 -500 65 -598 65 -462 165 -166 133 -198 131 -130 165 -66 129 -298 65 -332 925 -230 229 -68 36619 -16432 131 -66 231 -230 165 -260 429 -330 65 -66 131 -98 497 -98 299 -130 34117 -13338 99 -2454 65 -360 295 -330 65 -98 65 -362 131 -264 327 -132 25455 -12146 97 -166 99 -264 131 -300 99 -1132 65 -1162 63 -166 65 -534 67 -302 163 -200
|
||||
RAW_Data: 131 -100 263 -198 197 -100 997 -98 13019 -266 277 -326 275 -318 283 -322 287 -286 287 -322 251 -322 287 -286 285 -324 287 -288 283 -308 277 -320 271 -316 285 -286 287 -326 287 -290 287 -290 287 -326 289 -288 289 -288 289 -324 287 -288 287 -288 285 -308 275 -320 273 -318 283 -288 287 -326 287 -290 289 -288 289 -324 289 -288 289 -290 287 -324 287 -288 287 -286 273 -310 285 -304 317 -284 287 -324 253 -324 289 -288 289 -324 253 -326 287 -290 287 -290 287 -324 287 -288 285 -322 271 -278 285 -340 283 -286 285 -326 287 -290 287 -290 287 -326 253 -324 289 -288 287 -324 251 -324 287 -288 285 -308 277 -320 271 -316 285 -286 287 -326 287 -288 289 -288 289 -324 289 -290 287 -290 287 -324 287 -288 287 -288 275 -308 283 -304 315 -286 285 -326 287 -290 289 -288 289 -324 253 -324 289 -290 287 -290 287 -324 287 -288 287 -322 271 -278 317 -308 283 -284 287 -324 287 -288 289 -288 287 -326 287 -290 287 -290 289 -324 287 -288 287 -286 285 -308 277 -320 271 -318 285 -286 287 -326 289 -288 289 -288 289 -324 289 -288 289 -290 287 -324 287 -288 287 -288 285 -306 277 -322 271 -318 285 -286 323 -288 289 -286 289 -324 289 -288 287 -288 287 -324 251 -324 287 -288 285 -308 277 -318 271 -318 285 -286 287 -326 289 -288 289 -288 289 -324 289 -288 289 -290 287 -324 287 -288 287 -286 277 -308 283 -304 315 -284 287 -322 289 -288 287 -290 287 -324 287 -288 289 -288 287 -324 287 -288 285 -322 271 -278 285 -340 281 -286 287 -324 287 -290 287 -290 287 -326 253 -324 289 -288 287 -324 251 -324 287 -288 285 -306 277 -318 273 -318 283 -288 287 -324 289 -288 289 -324 251 -326 289 -288 287 -288 287 -324 287 -286 285 -308 277 -318 271 -316 285 -286 287 -326 287 -290 287 -290 287 -326 289 -288 289 -288 287 -324 287 -288 285 -322 271 -278 285 -338 281 -286 287 -324 287 -290 287 -290 287 -326 289 -288 289 -288 287 -324 287 -288 285 -288 283 -306 279 -320 273 -318 285 -286 287 -326 287 -290 287 -290 287 -326 289 -288 289 -288 287 -324 287 -288 287 -322 239 -308 285 -340 281 -286 285 -594 571 -616 583 -578 575 -312 319 -286 287 -286 321 -562 293 -300 275 -320 581 -318 287 -288 287 -568 291 -312 593 -316 287 -288 287 -290 287 -326 287 -290 289 -288 289 -324 287 -564 581 -304 313 -588 575 -586 293 -278 287 -342 549 -606 309 -286 285 -322 271 -278 285 -340 549 -316 283 -598 273 -322 285 -324 269 -278 285 -304 583 -620
|
||||
RAW_Data: 277 -320 287 -286 561 -322 293 -578 577 -314 285 -600 289 -284 297 -316 581 -588 589 -582 599 -584 281 -322 287 -288 287 -324 563 -580 599 -282 285 -324 287 -598 561 -292 309 -592 279 -322 287 -288 579 -588 579 -306 315 -588 281 -286 577 -308 283 -306 279 -322 273 -620 575 -312 283 -288 283 -596 293 -298 275 -318 583 -318 283 -109578 163 -100 65 -98 67 -100 493 -98 99 -66 165 -66 195 -164 1775 -130 15983 -12602 99 -534 165 -1354 65 -794 233 -166 99 -266 263 -64 723 -100 163 -64 389 -492 361 -66 589 -66 131 -132 47739 -6212 67 -494 67 -398 133 -826 229 -262 97 -262 493 -528 97 -66 165 -100 65 -100 131 -68 3645 -10814 65 -2378 163 -260 97 -100 97 -166 99 -1128 99 -630 165 -164 63 -132 395 -262 887 -100 63 -296 2211 -15018 65 -196 291 -3952 131 -202 133 -530 133 -334 99 -662 495 -132 527 -296 659 -13924 65 -458 99 -594 231 -98 467 -100 231 -268 131 -366 133 -132 963 -66 14609 -12472 65 -662 65 -298 135 -696 65 -326 99 -1514 629 -68 99 -430 297 -64 99 -132 129 -428 90861 -12490 65 -534 99 -264 97 -496 131 -766 99 -98 65 -1526 131 -102 297 -100 163 -130 131 -262 163 -230 229 -100 1379 -132 163 -130 50133 -12040 65 -2874 165 -330 65 -428 129 -100 789 -166 161 -132 165 -100 1415 -132 4311 -14480 101 -1330 197 -132 65 -426 555 -164 295 -232 2213 -12734 165 -1026 65 -264 67 -1948 131 -168 333 -132 65 -68 131 -134 165 -168 197 -280 307 -280 297 -280 311 -278 309 -278 321 -274 321 -250 303 -312 275 -320 309 -286 279 -276 311 -276 319 -276 323 -250 305 -310 277 -320 309 -288 279 -274 311 -276 319 -278 323 -248 305 -312 275 -320 311 -288 279 -276 311 -276 319 -276 321 -250 335 -282 307 -290 311 -286 277 -276 307 -280 321 -276 321 -248 305 -312 275 -320 311 -288 279 -276 309 -278 319 -274 321 -280 275 -312 307 -290 309 -288 279 -274 311 -278 319 -276 323 -250 305 -310 277 -320 309 -286 279 -276 309 -278 321 -278 313 -276 309 -278 317 -274 321 -250 303 -312 277 -318 309 -286 279 -276 309 -278 319 -276 323 -250 303 -312 277 -318 311 -288 279 -274 311 -276 321 -276 323 -250 303 -312 275 -320 309 -288 279 -274 311 -276 321 -276 323 -250 335 -280 307 -292 311 -286 277 -276 309 -278 321 -276 321 -252 303 -312 275 -320 309 -286 279 -276 309 -278 319 -276 321 -280 273 -312 307 -290 311 -288 279 -276 311 -276 319 -276 323 -250 305 -310 277 -318 309 -288 279 -276 311 -278 319 -276
|
||||
RAW_Data: 323 -248 337 -280 307 -290 311 -284 277 -278 309 -278 321 -276 323 -248 305 -312 275 -320 309 -288 279 -274 311 -276 319 -276 321 -280 275 -310 307 -290 313 -288 279 -274 311 -278 319 -274 321 -280 275 -310 277 -320 311 -288 279 -274 311 -278 321 -276 321 -252 333 -282 309 -256 343 -286 277 -276 309 -280 319 -274 321 -250 303 -312 275 -320 311 -286 279 -274 311 -278 319 -276 323 -280 273 -312 275 -322 311 -288 279 -274 311 -278 317 -276 321 -280 273 -312 307 -290 311 -288 277 -276 311 -278 321 -276 321 -252 335 -282 307 -256 343 -284 279 -276 309 -278 317 -276 321 -280 273 -312 307 -290 311 -288 279 -274 311 -276 319 -278 321 -280 275 -310 277 -320 311 -286 279 -274 311 -278 319 -276 323 -280 305 -280 307 -290 309 -286 279 -276 309 -280 323 -276 321 -250 335 -282 275 -288 343 -284 279 -274 311 -276 319 -276 321 -280 273 -312 307 -290 311 -290 277 -276 311 -276 319 -278 321 -248 305 -312 275 -320 311 -286 279 -274 311 -278 319 -276 323 -280 305 -280 307 -588 581 -566 601 -594 611 -284 291 -292 289 -292 327 -540 307 -288 309 -276 615 -284 323 -256 325 -550 313 -288 599 -288 281 -300 319 -288 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 327 -542 619 -260 321 -276 319 -288 289 -290 289 -598 283 -292 327 -254 611 -580 291 -300 315 -286 291 -288 327 -254 585 -282 325 -572 309 -272 601 -278 323 -288 291 -290 291 -290 327 -256 325 -548 315 -290 327 -254 327 -290 291 -292 595 -550 609 -588 317 -290 289 -292 571 -292 307 -292 313 -556 321 -288 579 -314 287 -292 289 -292 289 -290 307 -562 309 -320 279 -276 597 -294 299 -280 323 -572 283 -308 607 -580 603 -258 311 -592 283 -280 597 -580 609 -284 307 -278 309 -574 315 -288 325 -256 585 -318 289 -108466 97 -690 65 -228 129 -132 131 -132 333 -466 133 -98 199 -464 97 -166 623 -66 1121 -100 5011 -10622 99 -2108 163 -662 65 -1194 97 -200 67 -1426 197 -298 559 -98 161 -132 229 -132 665 -98 99 -100 99 -166 56085 -12390 99 -364 165 -698 133 -2924 165 -234 429 -132 133 -266 395 -228 527 -12146 99 -430 97 -2176 97 -958 65 -132 65 -332 199 -66 133 -132 3349 -12570 165 -624 129 -298 131 -428 65 -726 197 -64 229 -198 65 -262 491 -66 525 -12800 131 -364 99 -994 65 -564 131 -164 63 -66 197 -166 97 -426 131 -98 163 -64 97 -494 657 -66 99 -98 65 -166 16239 -13256 131 -3106 165 -66 165 -130 525 -66 229 -198 197 -234 731 -100
|
||||
RAW_Data: 131 -66 229 -232 99 -132 97 -198 80931 -12592 97 -1326 67 -2316 1617 -66 335 -98 2385 -66 54519 -2634 133 -2976 397 -758 291 -262 333 -198 465 -1526 131 -100 297 -364 97 -332 363 -100 163 -134 633 -962 99 -298 365 -796 65 -198 97 -66 295 -164 593 -66 131 -66 133 -66 297 -68 8537 -12114 131 -436 65 -1628 133 -1726 399 -66 331 -68 195 -266 131 -66 299 -134 131 -66 831 -98 461 -100 261 -15272 99 -432 65 -696 65 -328 165 -360 131 -492 165 -196 97 -426 99 -660 163 -232 129 -164 623 -100 327 -98 1021 -7942 371 -288 251 -306 279 -310 283 -304 317 -282 275 -312 277 -320 309 -284 279 -276 311 -280 321 -276 319 -252 303 -314 277 -320 309 -284 279 -276 311 -278 319 -274 319 -280 275 -312 277 -320 275 -322 279 -276 311 -278 319 -276 319 -278 277 -310 279 -320 309 -286 281 -276 311 -278 317 -276 321 -280 277 -310 279 -318 309 -284 279 -278 309 -280 321 -276 319 -282 273 -314 277 -318 273 -320 281 -276 311 -278 319 -274 319 -282 275 -312 277 -320 309 -286 279 -276 313 -278 317 -274 321 -282 275 -312 277 -320 273 -320 279 -278 311 -278 319 -274 319 -282 273 -312 309 -292 311 -284 287 -288 291 -288 327 -288 291 -290 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 287 -290 287 -290 307 -276 317 -274 317 -288 287 -292 291 -290 327 -290 291 -292 289 -292 289 -292 327 -252 327 -290 289 -290 289 -290 325 -254 325 -288 289 -288 289 -288 307 -276 317 -272 319 -288 287 -292 289 -328 289 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 289 -288 289 -278 305 -284 337 -282 287 -288 327 -252 327 -290 291 -288 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 287 -290 287 -290 307 -276 317 -274 317 -288 287 -290 289 -328 291 -290 291 -292 289 -292 289 -290 327 -290 291 -288 291 -290 289 -290 289 -292 323 -290 287 -290 287 -290 307 -276 317 -272 319 -288 287 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 287 -290 287 -276 309 -284 337 -282 287 -286 327 -252 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 289 -288 289 -324 271 -276 319 -276 319 -288 287 -292 289 -292 327 -290 291 -288 291 -288 291 -290 325 -290
|
||||
RAW_Data: 289 -290 291 -288 291 -290 327 -292 289 -292 289 -290 287 -290 287 -308 275 -318 273 -320 287 -290 287 -292 325 -292 291 -290 291 -290 291 -580 589 -580 605 -580 601 -274 307 -278 317 -276 319 -592 283 -288 291 -292 609 -276 321 -270 277 -616 283 -288 585 -316 287 -288 291 -328 289 -292 289 -290 287 -290 287 -308 275 -614 581 -588 311 -288 289 -288 595 -582 311 -286 287 -288 587 -316 287 -290 291 -288 325 -564 577 -606 281 -326 289 -292 291 -290 291 -290 581 -588 289 -286 305 -316 585 -590 559 -612 595 -582 279 -280 595 -306 317 -558 317 -288 581 -310 285 -598 291 -292 277 -320 575 -300 277 -310 293 -576 577 -622 581 -290 295 -312 285 -592 285 -286 601 -588 581 -294 297 -582 317 -274 599 -290 293 -312 285 -576 589 -578 603 -282 309 -280 309 -282 299 -578 317 -276 277 -310 611 -282 281 -108220 263 -98 133 -1128 65 -1396 133 -660 65 -196 295 -64 5131 -13276 331 -168 65 -134 99 -464 133 -66 131 -696 297 -100 399 -166 429 -66 2355 -9066 99 -896 197 -566 65 -992 131 -1024 789 -166 99 -166 99 -66 167 -132 397 -132 1591 -66 5853 -12270 65 -764 65 -3156 133 -166 231 -334 863 -334 931 -13462 65 -2336 165 -924 65 -132 133 -532 99 -366 99 -66 165 -262 459 -166 231 -134 665 -430 993 -15638 65 -400 97 -264 65 -1152 327 -100 327 -98 295 -66 99 -98 199 -368 495 -166 399 -66 165 -66 199 -132 99 -100 331 -232 43869 -12396 99 -394 97 -360 129 -66 133 -302 297 -102 165 -466 565 -400 329 -66 1023 -66 11819 -66 427 -12330 133 -562 67 -632 133 -1128 131 -1430 131 -100 131 -198 131 -132 131 -230 595 -268 527 -132 1083 -100 359 -66 32479 -15100 65 -460 65 -694 99 -166 895 -132 99 -66 131 -396 429 -15252 231 -3612 65 -230 233 -66 163 -100 163 -66 97 -196 163 -130 691 -15158 131 -232 67 -696 165 -1496 197 -232 397 -100 97 -230 429 -100 163 -130 195 -66 2301 -98 63277 -13394 65 -1282 163 -230 229 -298 1487 -164 197 -166 853 -98 3459 -12714 67 -166 65 -366 99 -166 165 -398 163 -2162 163 -234 165 -826 197 -66 529 -166 861 -100 66425 -14770 863 -232 561 -100 51695 -12382 131 -894 133 -428 233 -1680 131 -262 63 -164 261 -66 195 -164 97 -362 265 -68 32193 -15454 197 -326 197 -326 65 -232 165 -424 331 -98 627 -64 61125 -10356 359 -284 301 -318 285 -290 289 -290 327 -290 291 -292 289 -292 289 -292 291 -290 327 -288 291 -290 289 -290 289 -290 323 -290 289 -288 289 -288 325 -272 275 -318 273 -320 287 -288
|
||||
RAW_Data: 289 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 289 -290 289 -288 323 -272 275 -320 309 -282 289 -286 291 -288 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 287 -290 289 -288 309 -276 283 -304 317 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -288 327 -288 291 -290 289 -290 289 -290 291 -290 327 -292 289 -288 289 -290 287 -324 271 -276 319 -274 319 -288 289 -290 291 -328 289 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -290 291 -292 289 -292 327 -290 291 -288 289 -288 287 -308 275 -286 339 -284 287 -288 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -290 287 -290 287 -324 271 -278 317 -274 319 -288 289 -288 327 -290 291 -290 289 -290 289 -292 327 -252 327 -288 291 -292 289 -292 289 -292 291 -326 291 -292 289 -290 287 -290 287 -324 271 -278 317 -276 319 -286 289 -290 291 -328 289 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -290 325 -290 287 -290 287 -290 321 -274 275 -286 305 -318 285 -288 291 -290 291 -328 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -288 289 -288 289 -324 271 -276 319 -274 319 -288 287 -290 327 -288 291 -288 291 -288 291 -290 325 -290 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 289 -288 289 -288 307 -276 283 -306 317 -288 287 -292 291 -326 291 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -288 327 -288 289 -288 289 -288 323 -272 277 -284 305 -318 285 -288 289 -292 325 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -602 579 -602 579 -590 589 -290 283 -302 317 -286 287 -594 281 -326 287 -292 571 -294 277 -322 273 -586 317 -288 579 -310 285 -288 307 -278 317 -272 319 -288 287 -290 289 -328 289 -564 601 -310 269 -600 275 -320 585 -590 559 -292 311 -294 313 -556 611 -276 323 -564 581 -308 317 -552 317 -276 309 -278 317 -274 583 -318 275 -598 589 -312 285 -574 605 -290 293 -312 285 -574 573 -290 323 -576 577 -310 275 -310 283 -302 319 -572 285 -308 575 -612 571 -294 277 -322 309 -556 611 -560 607 -600 581 -570 605 -586 579 -612 587 -582 291 -298 579 -588 601 -582
|
||||
RAW_Data: 587 -314 287 -286 327 -252 327 -288 291 -582 277 -322 289 -288 563 -292 323 -111606 97 -892 97 -952 65 -164 65 -528 131 -992 297 -168 199 -334 163 -132 1381 -66 119801 -12434 99 -1284 99 -1192 99 -366 99 -300 361 -66 365 -498 231 -132 65 -268 131 -66 65 -68 723 -12268 131 -332 67 -1690 133 -1524 891 -266 133 -200 99 -166 165 -196 533 -132 13913 -11840 199 -398 99 -300 65 -666 131 -1822 199 -336 199 -332 659 -298 65 -132 65 -66 1319 -16402 99 -3918 131 -230 97 -66 163 -66 293 -198 295 -298 131 -132 559 -68 27795 -12096 131 -1232 131 -134 131 -232 65 -728 165 -100 197 -398 491 -130 25691 -16462 133 -130 129 -66 65 -130 527 -98 523 -264 661 -66 1189 -66 18249 -12996 65 -464 65 -2192 131 -404 97 -496 231 -394 131 -132 359 -98 14731 -10464 65 -1946 65 -330 65 -1594 97 -428 97 -952 165 -64 99 -164 423 -232 229 -458 429 -134 1161 -16758 65 -732 199 -1296 693 -264 97 -228 263 -132 99 -164 131 -100 1645 -66 16161 -11358 67 -1362 165 -366 131 -3068 99 -230 199 -100 465 -198 491 -166 231 -66 131 -98 15295 -12786 97 -658 99 -66 65 -264 131 -526 131 -360 327 -330 491 -362 331 -98 885 -98 329 -66 3347 -11292 131 -1128 67 -1526 65 -1594 99 -268 133 -232 99 -166 131 -66 65 -132 165 -100 133 -100 131 -202 9381 -14558 131 -960 99 -760 97 -98 263 -266 199 -66 431 -98 99 -196 327 -66 2133 -162 32333 -12264 97 -1976 65 -1628 165 -298 231 -296 99 -198 163 -64 163 -98 129 -362 1677 -100 99 -166 11219 -12634 163 -196 97 -526 129 -2406 329 -330 131 -296 129 -298 165 -264 265 -232 1097 -264 661 -100 11377 -1160 365 -280 323 -248 305 -310 277 -318 309 -288 279 -274 311 -278 319 -276 323 -280 273 -312 275 -322 309 -288 279 -274 311 -278 319 -276 321 -280 305 -280 307 -292 309 -286 277 -278 307 -280 323 -276 321 -250 303 -312 277 -320 309 -288 279 -274 311 -276 319 -276 321 -280 273 -312 277 -320 309 -288 279 -274 311 -278 319 -278 321 -280 273 -312 277 -320 309 -288 279 -274 311 -278 319 -274 323 -280 305 -282 307 -290 309 -286 279 -276 309 -280 321 -276 321 -250 303 -312 277 -318 311 -286 279 -274 311 -278 319 -276 321 -280 273 -312 307 -290 311 -288 279 -276 309 -278 319 -276 321 -280 273 -312 275 -322 311 -286 279 -276 311 -278 319 -276 321 -252 335 -280 309 -290 309 -286 277 -276 309 -278 319 -276 321 -280 275 -310 277 -320 309 -286 279 -276 311 -276 321 -276 323 -278 275 -312 275 -320 311 -286
|
||||
RAW_Data: 279 -276 309 -278 319 -278 321 -280 275 -310 277 -320 309 -288 279 -274 311 -280 321 -276 321 -252 335 -282 307 -256 343 -284 277 -276 311 -278 319 -274 321 -250 305 -310 277 -320 309 -288 277 -276 311 -276 321 -278 321 -280 273 -312 277 -318 311 -286 279 -276 309 -278 319 -278 321 -280 275 -312 275 -320 309 -288 277 -278 309 -278 321 -276 323 -250 335 -282 307 -258 341 -286 279 -276 311 -276 317 -276 321 -280 275 -310 277 -320 309 -288 279 -276 311 -276 321 -274 321 -280 275 -310 309 -290 311 -286 279 -276 311 -276 319 -276 321 -280 273 -312 307 -292 311 -286 277 -278 309 -278 321 -276 321 -252 335 -280 277 -318 309 -288 279 -274 311 -278 319 -274 321 -280 275 -310 277 -320 311 -288 279 -274 311 -278 319 -276 321 -278 275 -312 275 -320 313 -286 279 -276 309 -278 319 -276 321 -280 305 -282 307 -292 311 -286 289 -288 327 -254 325 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 289 -290 307 -274 317 -274 321 -288 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -564 577 -606 581 -590 587 -290 283 -304 319 -288 289 -588 283 -288 327 -290 573 -310 271 -278 319 -582 317 -290 581 -276 325 -288 289 -288 323 -242 307 -284 305 -320 287 -290 289 -598 575 -312 289 -290 291 -288 325 -558 587 -582 613 -590 579 -586 577 -590 599 -310 271 -600 579 -318 289 -580 591 -578 303 -278 323 -288 587 -578 587 -290 285 -304 319 -288 287 -292 291 -290 327 -562 591 -276 325 -292 289 -292 289 -596 289 -296 313 -288 585 -558 313 -288 601 -578 599 -588 609 -552 289 -288 607 -316 291 -292 289 -292 289 -604 277 -290 599 -576 603 -286 323 -550 315 -288 569 -612 597 -284 289 -290 327 -562 317 -290 289 -292 571 -292 309 -110276 99 -100 131 -1498 197 -194 195 -294 365 -134 45801 -12140 65 -264 99 -266 131 -194 261 -832 131 -500 99 -366 97 -232 233 -134 199 -298 133 -200 431 -396 265 -100 4707 -15340 65 -1022 199 -134 429 -132 133 -368 65 -860 163 -462 263 -66 265 -132 43085 -12134 97 -436 131 -700 65 -1392 197 -168 99 -466 165 -400 363 -260 165 -262 195 -66 165 -264 165 -132 131 -66 431 -12178 97 -1810 129 -990 101 -1130 165 -200 231 -100 495 -664 2221 -12140 65 -730 165 -566 99 -2972 361 -164 427 -198 65 -64 197 -360 231 -15180 131 -894 163 -924 97 -360
|
||||
RAW_Data: 197 -100 363 -166 99 -168 199 -266 99 -166 265 -8228 229 -360 199 -3710 65 -500 165 -932 67 -564 65 -632 97 -166 497 -202 30515 -11988 133 -702 165 -522 65 -2378 131 -360 263 -98 197 -132 165 -526 71849 -12006 97 -330 97 -162 97 -294 131 -988 63 -1120 163 -266 299 -166 1427 -66 331 -132 67 -166 29617 -264 309 -322 287 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 289 -292 289 -290 323 -242 307 -286 341 -284 289 -290 291 -288 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -288 309 -274 315 -274 319 -290 289 -292 327 -254 327 -254 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -288 307 -276 317 -274 321 -290 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -290 291 -288 307 -276 315 -276 321 -288 291 -292 289 -292 327 -254 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 323 -242 307 -282 337 -282 289 -290 291 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -288 325 -272 275 -286 341 -286 289 -290 327 -252 327 -254 327 -292 289 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -288 307 -276 315 -276 319 -290 289 -292 291 -290 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -290 289 -288 309 -274 317 -276 321 -288 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -290 289 -288 325 -242 305 -284 339 -286 287 -290 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -288 307 -276 317 -274 319 -288 291 -288 327 -254 325 -292 289 -292 291 -290 291 -290 291 -578 587 -614 575 -588 603 -276 305 -276 317 -274 321 -554 317 -290 325 -256
|
||||
RAW_Data: 603 -278 323 -292 291 -564 289 -288 609 -318 289 -290 289 -292 327 -254 327 -254 327 -292 289 -292 289 -570 287 -310 591 -588 319 -288 291 -290 291 -292 325 -256 327 -254 327 -290 291 -290 561 -614 309 -286 289 -290 587 -590 589 -290 279 -600 319 -292 583 -278 325 -252 327 -566 577 -608 581 -318 287 -574 579 -296 301 -584 581 -590 617 -582 575 -590 317 -288 291 -292 569 -292 307 -296 313 -556 615 -558 609 -304 313 -554 615 -564 611 -264 303 -586 319 -274 597 -586 311 -288 571 -570 615 -580 579 -282 307 -308 293 -312 289 -572 283 -308 279 -302 585 -318 273 -8042 343 -248 313 -276 309 -280 317 -276 321 -248 305 -310 277 -318 309 -288 277 -276 311 -278 317 -276 321 -280 275 -310 277 -320 313 -286 279 -276 309 -278 319 -274 321 -280 275 -312 307 -290 311 -288 279 -274 311 -278 321 -276 321 -250 335 -282 309 -256 343 -286 277 -276 309 -280 319 -274 321 -250 305 -312 275 -320 309 -288 277 -276 311 -276 319 -278 321 -280 275 -310 277 -320 309 -286 279 -276 311 -276 321 -276 323 -278 275 -312 275 -320 309 -288 277 -276 309 -280 323 -276 321 -250 335 -282 277 -288 341 -286 277 -278 309 -278 317 -276 321 -280 275 -310 277 -320 311 -286 279 -276 309 -278 319 -278 321 -280 275 -310 277 -320 309 -286 279 -276 311 -276 321 -276 323 -278 275 -312 307 -290 311 -286 279 -276 309 -278 321 -274 313 -278 309 -278 317 -274 321 -248 305 -310 277 -320 309 -286 279 -276 309 -278 319 -276 323 -278 275 -312 275 -320 311 -286 279 -276 309 -278 319 -278 321 -280 275 -310 277 -320 309 -288 277 -276 311 -278 319 -278 321 -288 289 -290 327 -252 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 289 -288 307 -276 317 -274 321 -290 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 289 -288 307 -276 317 -276 321 -288 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 325 -256 327 -254 327 -290 291 -290 289 -290 325 -242 305 -282 303 -318 289 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 323 -242 305 -284 339 -284 289 -288 327 -252 327 -254 327 -292 289 -292 291 -290 291 -290
|
||||
RAW_Data: 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 287 -290 307 -274 317 -276 321 -288 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 327 -254 327 -542 615 -586 575 -590 601 -274 307 -276 319 -276 321 -560 317 -288 327 -256 577 -308 325 -240 305 -612 283 -290 585 -318 289 -292 289 -292 289 -292 327 -290 291 -290 289 -290 325 -560 289 -296 579 -578 315 -288 327 -254 327 -292 289 -292 289 -292 289 -290 323 -274 595 -578 319 -288 291 -292 581 -558 607 -304 315 -556 321 -290 581 -278 325 -290 291 -564 615 -578 581 -316 289 -572 613 -264 303 -582 579 -592 613 -582 577 -590 315 -288 325 -256 601 -258 309 -298 315 -554 611 -592 577 -304 313 -556 625 -564 613 -264 303 -582 319 -292 561 -602 309 -272 599 -578 579 -590 585 -290 321 -276 319 -290 289 -564 317 -288 327 -254 611 -274 307 -110098 165 -102 99 -134 99 -3910 97 -392 131 -392 131 -198 261 -198 165 -98 821 -264 3117 -66 19797 -12076 131 -562 99 -632 97 -964 97 -298 67 -1322 197 -396 295 -100 229 -262 165 -164 2077 -12104 195 -722 99 -300 65 -1444 131 -560 129 -100 163 -98 163 -66 263 -98 131 -98 99 -330 363 -68 859 -17108 99 -2360 263 -396 265 -100 331 -132 65 -100 99 -232 65 -168 99 -168 233 -334 365 -66 163 -100 3813 -11168 99 -566 65 -3208 99 -330 361 -398 231 -362 65 -66 131 -230 231 -98 11143 -12652 165 -602 65 -400 233 -298 99 -1862 331 -132 467 -132 857 -232 4377 -9996 67 -2642 231 -232 65 -1150 99 -230 331 -200 829 -298 1811 -66 80961 -13390 65 -1292 133 -470 99 -730 99 -100 167 -236 265 -66 199 -268 197 -200 231 -164 165 -100 1495 -16432 167 -398 133 -334 101 -100 65 -164 163 -266 133 -134 895 -100 697 -232 5181 -12060 133 -1160 231 -366 197 -394 361 -664 131 -438 99 -234 99 -166 397 -334 395 -66 25975 -12940 99 -594 67 -728 133 -1218 459 -360 887 -66 263 -16440 231 -232 97 -264 65 -558 163 -300 329 -134 19759 -252 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -288 307 -276 315 -276 319 -290 289 -292 291 -290 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 289 -290 323 -242 305 -284 305 -318 289 -290 291 -290 291 -292 325 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 327 -254
|
||||
RAW_Data: 327 -290 291 -292 289 -292 325 -254 305 -276 317 -274 319 -288 289 -290 325 -256 325 -256 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 323 -254 307 -276 317 -276 321 -288 291 -290 327 -256 325 -256 325 -292 291 -290 291 -290 327 -256 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 287 -290 307 -276 317 -274 321 -290 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 315 -276 319 -290 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 287 -290 323 -274 275 -288 341 -284 289 -290 289 -290 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -288 307 -276 315 -272 321 -288 291 -290 327 -254 325 -292 291 -288 291 -290 327 -256 325 -256 327 -290 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -290 307 -274 317 -274 319 -290 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -290 289 -288 307 -276 317 -276 321 -288 289 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -570 609 -568 617 -590 591 -260 311 -296 317 -286 291 -588 283 -290 327 -290 571 -312 271 -276 321 -576 321 -288 581 -278 323 -290 291 -292 289 -292 289 -290 307 -274 317 -276 319 -290 289 -588 283 -288 327 -292 569 -576 623 -552 319 -288 291 -290 577 -310 323 -558 587 -314 285 -564 611 -586 579 -592 313 -286 289 -292 585 -604 285 -282 299 -318 585 -580 311 -288 595 -552 305 -320 587 -576 311 -290 563 -290 319 -274 317 -560 317 -290 291 -290 577 -312 287 -290 307 -274 317 -576 589 -318 289 -572 577 -296 303 -318 287 -560 613 -590 309 -272 273 -320 309 -286 585 -592 591 -580 597 -580 577 -312 287 -288 289 -594 291 -300 315 -286 587 -288 289 -8078 289 -286 303 -320 287 -290 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -290 289 -288 307 -276
|
||||
RAW_Data: 317 -274 321 -288 289 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 325 -256 327 -254 327 -290 289 -290 289 -288 325 -242 305 -284 339 -286 289 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -290 325 -242 307 -284 337 -284 289 -290 289 -290 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 289 -290 289 -288 307 -276 317 -276 319 -290 289 -292 291 -290 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -288 325 -272 275 -316 309 -286 289 -288 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -290 289 -290 289 -288 325 -272 275 -318 307 -288 287 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 287 -290 323 -272 277 -286 341 -284 289 -290 289 -290 327 -254 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -288 307 -276 315 -272 321 -288 291 -288 327 -256 325 -292 291 -290 291 -288 327 -254 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -290 287 -290 307 -276 317 -274 321 -288 289 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -290 291 -292 325 -256 327 -254 327 -290 291 -290 289 -288 289 -290 307 -276 317 -274 321 -288 289 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -290 291 -570 611 -564 613 -590 591 -260 311 -298 315 -284 291 -596 283 -290 289 -292 603 -276 325 -290 291 -564 321 -292 575 -284 325 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -572 291 -308 295 -314 583 -578 591 -580 301 -280 321 -250 631 -260 309 -596 579 -316 287 -572 609 -562 613 -578 309 -288 289 -278 593 -604 319 -288 291 -290 581 -590 309 -272 595 -578 319 -292 581 -560 319 -278 597 -280 327 -288 291 -576 283 -308 281 -300 585 -318 289 -290 291 -290 327 -542 613 -292 295 -580 577 -316 287 -292 325 -566 577 -604 283 -288 327 -292 289 -292
|
||||
RAW_Data: 581 -590 577 -606 581 -570 603 -294 299 -278 323 -558 319 -290 289 -292 577 -312 287 -109140 197 -100 99 -166 163 -1460 65 -1088 65 -368 167 -168 395 -924 133 -66 165 -66 299 -102 233 -102 467 -11476 133 -496 99 -696 197 -532 99 -1060 99 -132 331 -332 133 -98 67 -494 995 -98 265 -100 397 -166 61829 -12044 131 -66 99 -2076 65 -230 131 -1518 131 -130 363 -166 131 -398 665 -100 52769 -12048 129 -1416 65 -2586 229 -528 331 -100 199 -132 163 -66 16587 -12884 99 -424 65 -786 165 -432 1297 -100 199 -134 65 -98 691 -15728 65 -294 99 -4348 99 -768 3679 -6236 129 -436 171 -414 171 -416 171 -416 207 -384 201 -380 193 -402 207 -348 243 -352 245 -350 243 -350 209 -372 219 -352 269 -312 279 -316 245 -354 245 -320 283 -320 247 -356 245 -306 281 -322 271 -316 283 -322 249 -324 285 -322 249 -324 285 -322 249 -322 285 -286 283 -306 279 -320 273 -318 283 -286 325 -292 289 -292 289 -290 289 -290 327 -288 291 -290 289 -290 289 -290 287 -326 287 -290 287 -290 287 -324 271 -278 319 -274 319 -286 289 -288 327 -290 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -324 289 -290 287 -288 287 -308 277 -286 303 -318 287 -286 327 -288 291 -288 291 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -292 291 -290 291 -324 289 -288 289 -288 289 -324 269 -278 317 -274 319 -286 287 -290 289 -328 291 -290 291 -292 289 -292 289 -292 327 -288 291 -288 291 -290 289 -290 323 -290 287 -290 287 -288 287 -308 275 -320 273 -318 285 -290 287 -328 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 291 -290 325 -288 289 -288 289 -288 323 -270 277 -322 309 -282 287 -288 289 -326 289 -290 289 -290 291 -290 291 -290 291 -328 289 -292 291 -290 291 -290 291 -288 325 -290 287 -290 287 -288 323 -272 275 -320 275 -320 285 -288 289 -328 289 -292 289 -292 291 -290 291 -290 291 -292 325 -292 289 -290 291 -288 291 -288 325 -288 289 -288 287 -288 321 -272 277 -320 275 -318 287 -288 289 -326 291 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -288 325 -288 289 -290 287 -288 307 -276 285 -306 317 -286 289 -288 327 -290 289 -290 289 -290 289 -290 327 -288 291 -292 289 -292 289 -292 289 -290 325 -288 289 -288 289 -288 307 -276 285 -304 319 -286 289 -288 289 -328 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 291 -290 291 -290
|
||||
RAW_Data: 291 -288 325 -290 287 -290 287 -288 305 -278 285 -304 317 -286 289 -288 291 -598 571 -618 579 -578 579 -314 285 -324 289 -288 289 -596 287 -294 275 -320 585 -284 285 -326 291 -568 309 -288 563 -290 323 -310 285 -286 291 -288 327 -288 291 -288 291 -288 291 -580 587 -614 573 -318 289 -290 291 -564 597 -294 277 -322 309 -552 611 -592 577 -602 579 -318 287 -570 307 -272 311 -292 577 -316 287 -288 327 -540 615 -292 295 -276 319 -558 611 -592 575 -304 277 -588 317 -288 599 -280 285 -326 287 -564 291 -288 605 -586 315 -288 569 -292 309 -590 281 -326 289 -292 581 -592 287 -280 297 -316 287 -288 289 -326 291 -290 291 -292 581 -278 321 -288 289 -564 291 -324 575 -318 287 -564 313 -288 599 -288 279 -298 315 -590 283 -288 289 -324 573 -310 271 -104568 131 -3524 365 -296 97 -130 131 -626 399 -100 397 -98 20515 -12064 195 -66 227 -756 65 -1958 567 -166 399 -232 361 -364 131 -298 627 -100 1951 -12080 97 -952 99 -1350 99 -894 229 -458 161 -66 295 -164 461 -198 4209 -12020 129 -490 131 -598 167 -2358 265 -132 131 -298 133 -266 131 -66 65 -362 131 -202 627 -198 3645 -13054 97 -1906 65 -1152 65 -328 99 -396 265 -98 1231 -66 97 -66 197 -66 31651 -12478 65 -492 99 -1064 65 -830 331 -996 99 -132 499 -332 97 -430 327 -66 1583 -164 327 -100 46383 -7186 65 -696 165 -298 129 -2890 431 -166 131 -130 65 -328 197 -66 1821 -96 23165 -12116 131 -132 65 -1154 131 -234 65 -398 131 -1120 99 -326 297 -68 729 -66 131 -66 961 -66 623 -98 97 -98 20383 -10172 169 -2328 99 -300 165 -200 63 -460 63 -628 197 -68 393 -132 97 -66 295 -594 99 -300 67 -232 3541 -12542 99 -728 165 -1524 99 -1188 163 -68 397 -134 429 -398 165 -98 165 -200 301 -12376 163 -164 197 -360 65 -2256 67 -264 595 -300 99 -134 167 -332 199 -332 1365 -66 15123 -13146 65 -1288 65 -2212 131 -98 229 -430 297 -362 165 -98 795 -200 9723 -12176 97 -264 195 -2510 131 -360 199 -534 529 -100 133 -100 167 -100 131 -168 329 -98 133 -166 201 -98 993 -16166 131 -328 65 -394 133 -298 65 -730 65 -66 199 -566 65 -98 329 -230 97 -228 263 -132 227 -164 197 -98 30203 -11420 131 -232 197 -2672 99 -134 65 -764 233 -300 131 -332 131 -168 233 -436 67 -66 331 -15318 263 -132 65 -3158 99 -268 461 -332 397 -164 363 -202 531 -66 2785 -12972 163 -1352 99 -1796 65 -66 1023 -264 199 -100 235 -100 865 -98 567 -66 35431 -14438 131 -198 99 -332 65 -1060 1553 -132 265 -234
|
||||
RAW_Data: 52093 -9826 415 -256 289 -290 289 -292 327 -290 291 -290 289 -290 289 -290 327 -288 291 -290 291 -290 289 -292 289 -292 327 -290 291 -288 289 -288 289 -324 271 -276 319 -274 319 -288 287 -292 289 -328 291 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -292 291 -290 327 -288 289 -288 289 -288 321 -272 277 -320 273 -320 287 -288 289 -290 327 -290 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 289 -290 287 -290 287 -308 277 -318 271 -320 287 -286 291 -288 327 -288 291 -290 289 -290 289 -290 327 -288 291 -288 291 -292 289 -292 289 -326 289 -290 289 -288 289 -324 271 -276 317 -274 319 -288 287 -290 289 -328 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -290 327 -288 291 -288 287 -290 321 -272 277 -286 303 -318 287 -288 287 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -290 287 -290 287 -308 277 -318 271 -320 285 -290 289 -290 327 -290 291 -288 291 -288 291 -290 325 -292 291 -290 291 -290 291 -292 289 -292 289 -328 289 -290 289 -288 289 -288 307 -276 317 -274 317 -288 289 -288 291 -328 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 291 -290 291 -290 291 -290 325 -290 287 -290 287 -288 285 -308 277 -286 305 -318 287 -288 289 -290 327 -292 289 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 291 -326 289 -292 287 -290 287 -288 307 -278 315 -274 319 -286 289 -288 327 -288 291 -288 291 -290 289 -290 327 -290 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 289 -288 289 -288 307 -276 283 -304 317 -288 289 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -290 325 -290 287 -290 287 -290 321 -274 275 -284 303 -318 287 -288 289 -290 327 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -326 289 -290 289 -288 289 -288 307 -276 317 -576 577 -596 607 -566 611 -282 325 -288 291 -292 289 -572 293 -278 321 -310 555 -318 287 -292 289 -602 311 -288 563 -290 287 -304 317 -288 287 -290 289 -328 291 -290 291 -290 291 -290 291 -602 275 -290 599 -290 283 -304 317 -556 611 -590 577 -602 283 -288 325 -292 547 -624 581 -584 311 -286 585 -284 321 -290 291 -568 309 -286 285 -308 277 -284 603 -586 601 -310
|
||||
RAW_Data: 271 -278 319 -276 321 -558 611 -590 287 -282 299 -318 285 -288 289 -290 587 -316 285 -570 291 -310 595 -586 317 -288 573 -308 271 -602 575 -318 287 -598 279 -288 599 -578 601 -584 281 -324 289 -292 571 -580 295 -306 315 -286 287 -290 289 -328 549 -316 287 -288 289 -600 289 -284 299 -316 585 -286 285 -109398 361 -164 457 -1134 99 -1662 165 -992 65 -460 131 -492 65 -362 263 -66 97 -66 165 -262 767 -100 1391 -10996 65 -1096 97 -392 163 -198 163 -560 65 -1324 99 -666 499 -66 331 -166 265 -300 1295 -66 15101 -12304 65 -2082 365 -728 133 -1358 299 -200 131 -432 165 -198 265 -164 1557 -100 9671 -12726 65 -234 65 -632 99 -300 99 -468 65 -1694 463 -200 231 -166 295 -298 295 -164 919 -66 5431 -12230 67 -264 365 -466 231 -362 229 -694 165 -1386 165 -366 99 -332 659 -166 133 -100 197 -68 71199 -12474 99 -100 263 -560 65 -294 97 -886 97 -886 97 -524 131 -894 231 -362 263 -98 757 -66 459 -18822 231 -462 329 -66 97 -230 261 -266 2237 -66 6549 -9878 197 -132 99 -23358 99 -1732 99 -728 165 -2090 65 -430 295 -98 327 -100 329 -98 1615 -15050 65 -328 165 -234 131 -1290 131 -564 99 -856 129 -490 625 -100 165 -66 363 -66 265 -166 1455 -100 1455 -12814 67 -2666 197 -232 97 -164 263 -100 101 -132 233 -98 99 -232 4167 -13454 99 -1120 195 -560 327 -1060 65 -432 197 -200 299 -66 165 -266 201 -234 897 -100 233 -100 565 -100 2283 -14860 99 -1686 725 -330 131 -230 197 -162 229 -134 623 -98 51277 -13322 199 -530 65 -860 97 -494 163 -526 65 -228 725 -198 331 -168 1627 -18878 129 -328 97 -100 65 -132 297 -266 199 -134 133 -98 99 -66 1419 -200 3387 -9820 99 -432 131 -1290 65 -1480 163 -560 65 -494 195 -66 463 -132 361 -132 165 -100 165 -134 431 -15428 67 -792 99 -400 65 -364 97 -800 65 -234 133 -362 165 -168 333 -132 229 -364 529 -68 263 -234 2047 -298 277 -320 285 -290 287 -328 289 -292 291 -290 291 -290 291 -292 325 -290 289 -290 291 -288 291 -288 327 -252 325 -290 287 -290 287 -288 307 -276 317 -274 317 -288 287 -290 289 -328 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -292 291 -290 291 -328 289 -290 289 -290 287 -290 285 -308 277 -320 309 -284 287 -286 289 -290 327 -288 291 -288 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 289 -328 289 -288 289 -288 289 -288 307 -276 285 -306 317 -288 287 -290 289 -328 291 -290 291 -292 289 -292 289 -292 291 -326 291 -290
|
||||
RAW_Data: 289 -290 289 -290 289 -326 287 -290 289 -288 287 -288 307 -278 283 -306 319 -286 289 -288 291 -326 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -326 287 -290 287 -290 287 -324 271 -276 319 -274 319 -286 289 -288 327 -288 291 -288 291 -290 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 289 -326 289 -288 289 -288 289 -288 307 -276 317 -274 317 -288 287 -288 327 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -288 291 -288 325 -288 289 -290 287 -288 287 -308 277 -284 303 -320 285 -288 289 -328 289 -292 289 -292 291 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 323 -290 287 -290 287 -288 287 -308 275 -320 273 -318 287 -290 287 -326 291 -288 291 -288 291 -288 327 -290 289 -292 291 -290 291 -290 291 -288 289 -326 287 -290 287 -288 287 -308 275 -320 273 -318 287 -288 289 -326 291 -292 289 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -288 289 -324 289 -288 287 -288 321 -272 277 -320 271 -318 287 -288 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -324 289 -290 287 -290 285 -324 271 -278 319 -308 285 -286 289 -288 327 -290 289 -292 289 -290 289 -290 327 -288 291 -290 289 -290 287 -290 323 -290 287 -290 287 -288 307 -278 283 -304 317 -286 287 -290 325 -292 291 -290 291 -290 291 -292 289 -292 289 -578 617 -584 575 -588 597 -294 277 -322 311 -284 287 -592 281 -326 287 -292 573 -308 271 -278 321 -578 317 -288 581 -312 285 -288 289 -288 307 -276 285 -304 319 -286 287 -290 325 -550 315 -286 567 -292 311 -596 317 -288 581 -592 575 -302 311 -284 289 -286 327 -288 291 -560 315 -288 287 -290 287 -326 287 -290 287 -288 595 -588 311 -284 287 -288 587 -586 585 -292 287 -306 319 -286 287 -290 287 -328 289 -292 291 -564 307 -278 597 -604 281 -282 309 -280 309 -296 579 -316 273 -282 309 -284 303 -316 281 -276 313 -278 319 -574 577 -314 277 -312 291 -578 317 -276 597 -292 297 -312 283 -282 275 -600 295 -302 581 -586 599 -582 589 -614 575 -312 285 -288 287 -596 289 -298 275 -322 583 -284 285 -8078 289 -282 299 -316 289 -288 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -288 325 -242 305 -286 339 -284 289 -288 327 -252 327 -254 327 -292 289 -292
|
||||
RAW_Data: 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -288 325 -272 275 -284 341 -286 287 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -288 307 -276 315 -272 321 -288 289 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -290 289 -288 287 -290 307 -276 317 -272 321 -288 289 -290 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -288 325 -242 307 -284 341 -284 289 -288 325 -254 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 323 -274 273 -318 307 -286 289 -290 291 -292 289 -292 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 317 -274 321 -288 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -290 289 -288 323 -242 305 -284 339 -286 287 -290 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 289 -290 289 -288 309 -276 317 -276 321 -286 291 -288 327 -252 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -288 289 -288 309 -276 317 -274 321 -288 289 -290 291 -292 325 -292 291 -290 291 -290 291 -292 325 -256 327 -254 327 -290 291 -292 289 -292 289 -570 609 -568 615 -590 589 -260 309 -300 315 -290 287 -596 285 -286 327 -292 573 -274 307 -276 319 -576 319 -290 583 -278 323 -290 291 -290 289 -288 323 -272 277 -286 307 -318 287 -560 319 -290 579 -310 289 -594 289 -294 579 -578 623 -276 323 -290 271 -278 317 -274 321 -560 317 -290 291 -290 327 -254 327 -292 289 -292 571 -580 295 -304 317 -288 585 -574 589 -290 317 -274 319 -288 289 -292 289 -292 291 -290 327 -548 315 -288 601 -576 303 -278 323 -288 289 -290 585 -284 323 -290 291 -292 289 -292 289 -292 327 -290 291 -564 613 -274 317 -286 279 -600 289 -280 595 -318 273 -278 309 -282 301 -584 317 -274 597 -588
|
||||
RAW_Data: 579 -578 601 -582 607 -282 309 -278 309 -574 313 -288 291 -290 587 -316 287 -107286 363 -232 303 -1198 67 -262 101 -564 65 -298 99 -1126 99 -826 97 -262 97 -132 161 -66 129 -164 97 -460 97 -130 131 -298 1381 -132 261 -130 112975 -12662 97 -590 97 -232 165 -2270 163 -296 193 -66 653 -66 233 -168 365 -100 67 -98 467 -12214 67 -432 331 -200 165 -532 99 -164 97 -166 99 -1656 397 -332 431 -166 431 -300 1527 -16098 65 -164 261 -130 391 -232 231 -100 231 -66 767 -12926 65 -2294 459 -164 459 -362 427 -132 655 -98 163 -132 131 -98 131 -66 6579 -14256 295 -100 167 -1396 267 -66 99 -66 133 -66 63 -264 99 -364 65 -100 329 -66 33937 -12358 327 -132 99 -856 131 -166 597 -66 131 -100 331 -100 295 -166 165 -66 65 -100 1493 -66 46185 -14054 65 -670 65 -664 99 -434 99 -132 233 -100 99 -430 363 -428 263 -64 197 -98 391 -250 325 -292 291 -290 291 -290 291 -292 325 -256 327 -254 327 -290 291 -292 289 -292 289 -290 307 -274 315 -274 321 -288 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -288 325 -242 305 -286 337 -284 287 -288 291 -290 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -288 309 -274 315 -274 319 -290 289 -290 327 -254 327 -290 291 -288 291 -290 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 323 -274 273 -286 341 -286 287 -290 289 -292 327 -254 327 -292 289 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -290 287 -290 325 -272 273 -318 309 -286 289 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 289 -288 325 -242 307 -286 341 -282 289 -288 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 287 -290 307 -276 317 -274 321 -290 289 -292 327 -254 327 -292 289 -290 289 -290 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 289 -290 289 -288 307 -276 317 -274 321 -288 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292
|
||||
RAW_Data: 327 -254 327 -254 327 -292 289 -292 287 -290 287 -290 307 -276 315 -276 319 -290 287 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 287 -290 325 -242 307 -284 339 -284 287 -290 325 -254 325 -292 291 -290 291 -290 291 -292 289 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -290 323 -272 275 -286 339 -286 289 -288 291 -586 605 -582 583 -580 611 -280 323 -252 327 -292 289 -564 291 -288 307 -320 585 -286 289 -290 327 -568 311 -288 561 -292 317 -274 319 -288 289 -290 291 -290 327 -292 289 -292 291 -580 313 -288 291 -290 291 -288 595 -590 577 -318 291 -564 601 -310 269 -598 311 -286 593 -286 289 -600 313 -252 599 -290 281 -602 581 -588 585 -290 321 -578 577 -592 309 -288 325 -288 559 -296 301 -584 319 -288 291 -290 291 -290 579 -588 611 -276 319 -558 319 -288 291 -290 579 -586 613 -578 319 -290 289 -290 289 -292 289 -292 603 -558 291 -310 295 -316 287 -288 587 -282 323 -572 307 -272 309 -290 313 -286 289 -290 587 -592 589 -290 279 -300 317 -554 317 -288 327 -254 603 -278 323 -110270 199 -366 197 -902 99 -1392 729 -232 165 -98 165 -132 97 -332 499 -66 199 -166 133 -134 167 -68 21291 -14098 99 -2236 131 -432 65 -100 99 -332 99 -532 1095 -12462 99 -898 65 -432 163 -332 65 -1424 431 -132 99 -362 199 -364 20857 -12296 197 -1088 131 -100 67 -900 97 -400 131 -100 99 -230 99 -130 297 -66 397 -132 1399 -98 73607 -12628 97 -860 131 -300 397 -134 131 -464 65 -300 199 -166 431 -100 395 -22870 199 -262 361 -66 495 -360 65 -262 165 -264 263 -98 22577 -12418 165 -460 99 -198 329 -100 163 -918 131 -264 63 -1286 199 -134 265 -66 197 -232 267 -68 263 -166 11113 -16396 393 -66 985 -196 165 -396 63 -198 855 -18276 65 -166 99 -336 199 -964 163 -100 129 -698 263 -400 1253 -66 9933 -12562 197 -564 99 -332 133 -66 529 -164 229 -260 97 -130 329 -13178 67 -1564 133 -366 165 -1032 131 -630 665 -68 229 -298 131 -164 15039 -12176 165 -494 97 -3648 97 -662 593 -298 427 -66 1489 -100 135 -100 34371 -12088 97 -1660 201 -398 99 -1700 623 -164 229 -96 425 -228 165 -164 6623 -12108 231 -1922 65 -1892 133 -234 265 -464 329 -98 197 -132 129 -166 295 -66 13551 -13534 165 -1266 65 -1498 99 -400 199 -298 165 -360 229 -66 231 -64 3477 -12408 67 -134 101 -664 67 -992 65 -1460 131 -168 135 -366 131 -600
|
||||
RAW_Data: 265 -302 1879 -66 13141 -286 331 -246 323 -288 327 -252 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 323 -272 275 -284 343 -286 289 -290 291 -290 327 -254 327 -292 289 -292 291 -290 327 -254 327 -256 325 -256 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -290 323 -272 275 -318 309 -288 287 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 327 -254 327 -254 327 -292 287 -292 287 -290 323 -242 305 -286 339 -286 289 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -290 289 -290 323 -242 307 -284 339 -286 289 -288 327 -252 327 -254 327 -292 289 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -288 325 -252 307 -276 319 -274 321 -290 289 -292 291 -290 327 -254 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -288 325 -272 275 -284 341 -286 289 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 327 -254 327 -254 327 -292 289 -290 289 -292 287 -290 307 -274 317 -276 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 287 -290 323 -242 307 -284 339 -284 289 -290 289 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -288 325 -272 275 -318 309 -286 287 -292 289 -292 327 -254 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 325 -292 291 -290 291 -290 291 -288 307 -276 315 -274 321 -288 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 325 -542 615 -586 577 -576 597 -292 309 -294 311 -288 287 -564 319 -288 327 -254 611 -276 323 -274 273 -614 319 -252 585 -282 307 -308 291 -314 287 -288 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -570 609 -300 315 -286 287 -560 319 -274 597 -590 579 -318 289 -566 595 -578 627 -284 287 -292 327 -254
|
||||
RAW_Data: 327 -548 315 -288 327 -254 601 -580 603 -586 605 -550 323 -292 309 -288 591 -558 313 -290 601 -258 309 -296 315 -554 319 -286 579 -586 321 -292 577 -588 317 -288 569 -574 627 -282 289 -586 603 -260 309 -298 317 -288 289 -290 325 -550 603 -582 585 -314 285 -290 289 -292 289 -598 319 -288 291 -290 573 -310 271 -109998 131 -466 65 -66 65 -924 163 -1428 299 -132 133 -332 465 -66 395 -302 1789 -10568 327 -264 163 -3976 99 -860 67 -1128 131 -362 197 -66 131 -166 131 -166 99 -164 917 -100 453 -66 323 -98 527 -100 1361 -100 2557 -12326 165 -398 233 -1128 99 -268 365 -130 163 -66 197 -464 263 -100 1349 -64 229 -98 657 -132 33955 -13018 131 -1820 133 -700 131 -302 463 -134 231 -266 197 -100 1797 -66 1225 -5802 131 -1482 97 -400 593 -100 99 -296 427 -198 367 -132 199 -134 1457 -100 21233 -14580 65 -100 197 -1064 65 -396 133 -168 331 -68 165 -134 67 -132 233 -66 565 -100 463 -134 595 -132 7073 -12378 65 -200 133 -430 65 -1396 329 -300 199 -164 165 -664 165 -132 331 -98 131 -132 863 -66 25629 -13220 99 -730 65 -496 133 -1058 131 -594 263 -98 133 -532 163 -196 293 -164 36707 -12300 99 -200 165 -298 297 -134 65 -892 129 -1858 131 -398 727 -198 299 -166 1061 -98 67 -166 197 -100 199 -66 107311 -11400 97 -528 65 -860 65 -496 133 -362 65 -1694 131 -434 131 -98 263 -100 993 -332 727 -12782 65 -2290 99 -590 99 -2488 131 -332 99 -732 99 -66 433 -266 99 -198 267 -562 1557 -66 41353 -13686 65 -66 97 -1856 131 -198 233 -332 265 -366 97 -132 99 -400 229 -66 2875 -11232 65 -1230 99 -1856 97 -2042 297 -98 327 -132 559 -196 889 -66 461 -230 753 -130 1345 -12540 131 -100 97 -232 65 -196 225 -2190 165 -98 133 -434 65 -132 399 -132 97 -100 559 -66 485 -1602 67 -1136 137 -414 173 -416 173 -416 171 -416 169 -414 195 -402 171 -416 209 -382 209 -384 173 -384 205 -378 225 -366 239 -382 209 -348 245 -350 243 -350 209 -370 219 -354 233 -380 209 -386 209 -352 247 -352 245 -354 245 -316 237 -346 259 -338 243 -352 249 -320 283 -320 247 -356 247 -320 283 -318 239 -312 285 -338 279 -284 283 -324 285 -286 285 -324 285 -286 285 -322 249 -322 283 -320 269 -312 257 -340 281 -284 285 -324 289 -288 289 -288 289 -324 287 -290 289 -290 291 -288 325 -288 289 -288 289 -288 305 -276 317 -274 317 -288 287 -290 289 -328 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -326 287 -290 289 -288
|
||||
RAW_Data: 289 -286 307 -278 317 -274 321 -286 287 -288 327 -288 291 -288 291 -288 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -328 291 -288 289 -288 289 -288 307 -276 283 -304 319 -288 287 -290 323 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -288 291 -288 325 -290 287 -290 287 -288 321 -272 277 -318 273 -318 285 -288 289 -328 289 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -290 323 -290 287 -290 287 -290 323 -270 277 -320 273 -320 287 -288 325 -290 291 -288 291 -288 291 -288 291 -326 289 -290 291 -290 291 -290 291 -288 289 -326 287 -290 287 -288 287 -308 275 -318 275 -318 287 -288 289 -326 291 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -324 289 -288 289 -288 287 -278 307 -284 303 -316 287 -288 289 -292 325 -292 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 289 -326 289 -288 289 -288 289 -324 269 -278 317 -274 319 -288 289 -288 291 -326 291 -290 289 -290 289 -290 327 -288 291 -288 291 -290 289 -290 287 -326 287 -290 287 -290 287 -322 271 -278 319 -276 319 -288 287 -288 291 -326 291 -292 289 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -292 291 -290 289 -324 289 -564 613 -574 579 -600 581 -294 301 -314 287 -288 287 -594 281 -324 289 -290 571 -292 277 -322 275 -590 315 -286 579 -312 285 -288 307 -276 317 -274 317 -288 287 -288 327 -290 291 -292 289 -292 289 -292 291 -576 617 -584 273 -320 583 -282 287 -602 589 -580 601 -586 281 -322 573 -308 271 -600 577 -586 315 -286 569 -610 597 -586 281 -324 289 -292 571 -308 271 -310 293 -578 613 -562 611 -264 307 -586 577 -622 581 -290 295 -310 285 -288 287 -290 327 -290 291 -562 601 -310 271 -276 321 -576 579 -314 285 -600 579 -302 279 -590 611 -274 305 -280 319 -272 319 -282 275 -602 587 -312 287 -288 289 -326 291 -292 289 -292 289 -582 311 -286 289 -288 593 -292 297 -103424 133 -704 129 -1332 135 -1558 99 -334 133 -200 493 -98 97 -132 229 -330 263 -12852 97 -992 97 -760 97 -890 461 -100 229 -162 695 -196 461 -134 73793 -12232 131 -296 131 -130 129 -1446 753 -100 99 -198 233 -664 1061 -15142 131 -700 97 -526 229 -398 131 -496 331 -98 629 -100 99 -166 399 -132 61387 -12480 165 -166 65 -894 65 -932 97 -1056 197 -362 165 -200 363 -134 959 -100 65 -166 723 -15080 97 -292 97 -132 99 -228 131 -796
|
||||
RAW_Data: 133 -1352 97 -392 293 -98 63 -198 2395 -196 723 -196 118245 -12016 99 -300 165 -98 65 -100 65 -466 99 -164 65 -330 261 -396 229 -98 165 -328 99 -134 20951 -14504 65 -134 65 -1856 265 -200 131 -532 99 -100 99 -462 997 -66 165 -68 891 -10520 65 -1798 67 -466 99 -100 65 -232 99 -962 267 -202 1061 -66 99 -134 1293 -196 363 -100 60031 -12620 65 -498 65 -166 99 -1496 99 -460 65 -754 393 -132 363 -66 265 -100 99 -362 263 -100 2359 -202 4033 -12162 199 -662 65 -1972 99 -364 97 -130 229 -920 131 -196 65 -98 263 -328 129 -132 623 -66 491 -98 8493 -12358 131 -404 97 -2514 165 -992 197 -66 395 -66 261 -100 295 -98 197 -66 31691 -310 283 -318 247 -356 247 -320 247 -354 239 -312 285 -338 243 -318 285 -322 285 -288 285 -322 249 -324 285 -324 251 -324 287 -288 285 -322 271 -276 319 -276 319 -286 289 -288 325 -290 291 -288 291 -290 291 -290 325 -290 289 -292 291 -290 291 -290 291 -290 325 -290 289 -290 287 -288 287 -308 275 -318 273 -322 285 -288 289 -328 289 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -288 291 -326 287 -290 287 -288 287 -306 277 -286 303 -318 287 -288 287 -292 327 -290 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -292 291 -324 289 -288 289 -288 287 -324 269 -278 321 -274 319 -286 289 -288 327 -292 289 -290 289 -290 291 -288 327 -288 291 -290 289 -290 289 -290 287 -326 287 -290 289 -288 287 -308 275 -318 273 -318 287 -288 289 -326 291 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -288 325 -288 289 -290 287 -278 307 -284 301 -316 287 -286 291 -288 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 289 -324 289 -290 287 -290 287 -288 307 -276 317 -274 319 -286 287 -288 327 -292 289 -292 289 -292 291 -288 291 -326 289 -290 289 -290 289 -290 323 -290 289 -288 289 -288 287 -324 271 -276 319 -274 317 -288 287 -290 325 -292 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -290 291 -292 289 -292 287 -326 287 -290 289 -288 287 -308 275 -318 309 -282 287 -288 327 -288 289 -290 289 -290 291 -290 291 -290 291 -328 289 -292 291 -290 291 -290 289 -288 325 -288 289 -290 287 -288 307 -276 317 -272 319 -286 287 -290 325 -292 291 -290 291 -290 291 -292 289 -292 289 -328 291 -288 291 -288 291 -290 323 -290 287 -290 285 -288 323 -272 277 -320
|
||||
RAW_Data: 273 -318 287 -288 289 -324 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 287 -290 287 -290 325 -288 289 -288 289 -288 307 -276 283 -306 317 -288 287 -288 327 -288 291 -290 289 -290 289 -290 325 -290 291 -288 291 -288 289 -288 325 -290 287 -564 615 -578 577 -590 585 -290 321 -274 319 -286 287 -586 315 -286 289 -288 601 -290 279 -298 315 -590 283 -288 575 -310 285 -308 277 -288 305 -316 287 -288 289 -290 327 -288 291 -290 291 -600 561 -578 593 -316 285 -290 325 -562 315 -286 291 -290 569 -612 299 -276 317 -288 287 -288 583 -316 287 -568 611 -300 273 -588 317 -274 599 -292 297 -278 319 -576 301 -278 277 -324 575 -318 275 -280 309 -578 315 -280 601 -580 601 -580 571 -292 311 -596 579 -310 277 -308 281 -296 315 -286 279 -308 281 -602 575 -608 573 -584 303 -316 551 -316 287 -600 589 -578 599 -586 281 -322 289 -290 571 -578 593 -314 285 -290 327 -290 291 -562 315 -288 287 -290 599 -288 283 -103474 231 -98 99 -432 65 -98 197 -100 99 -398 131 -332 65 -434 99 -434 65 -1032 297 -132 231 -100 233 -132 365 -332 199 -68 1093 -68 733 -15328 131 -294 65 -428 131 -856 131 -924 131 -98 99 -198 65 -66 133 -166 99 -198 199 -100 97 -66 363 -266 231 -336 329 -132 201 -12048 259 -198 65 -1124 131 -230 97 -428 97 -1412 165 -232 99 -164 65 -66 163 -66 133 -98 199 -794 561 -166 99 -100 1627 -66 20623 -15294 67 -594 97 -558 197 -66 99 -362 327 -166 199 -98 131 -262 99 -98 34085 -12508 99 -2986 65 -530 99 -366 165 -132 167 -132 265 -98 163 -98 65 -132 361 -164 165 -130 295 -100 297 -68 895 -200 34535 -13550 99 -364 231 -132 261 -164 65 -164 197 -98 63 -166 429 -132 661 -66 897 -66 3437 -12720 97 -232 131 -366 131 -366 199 -498 197 -1528 99 -300 231 -66 167 -66 165 -66 627 -100 163 -230 1871 -100 10635 -5246 65 -8124 65 -830 65 -2240 67 -400 99 -296 65 -164 393 -198 131 -98 919 -296 97 -98 263 -66 165 -100 161805 -12412 167 -166 67 -66 165 -856 163 -198 97 -1576 97 -98 131 -98 165 -294 231 -300 363 -236 201 -200 331 -132 1659 -17444 129 -1876 97 -232 97 -134 263 -134 97 -166 99 -66 99 -132 329 -132 7111 -12746 97 -1022 97 -530 165 -932 131 -66 99 -496 97 -232 559 -98 131 -98 493 -132 195 -66 163 -132 427 -132 30991 -12060 133 -1558 97 -1184 129 -822 723 -232 197 -330 165 -66 50235 -12078 101 -894 165 -134 65 -168 167 -298 65 -232 133 -196 67 -66
|
||||
RAW_Data: 133 -1690 1863 -132 983 -66 65463 -436 97 -10046 133 -1258 65 -702 233 -2088 65 -330 433 -464 297 -66 297 -658 163 -66 4989 -280 275 -318 273 -318 285 -288 287 -326 289 -288 289 -288 289 -326 287 -290 287 -290 287 -326 287 -288 289 -288 287 -322 269 -280 319 -308 283 -286 287 -326 287 -288 291 -288 289 -290 323 -290 287 -290 287 -288 289 -324 287 -288 287 -288 275 -308 285 -304 317 -286 285 -324 289 -290 287 -290 287 -326 251 -326 289 -288 289 -288 289 -324 289 -288 287 -322 239 -308 285 -306 315 -286 285 -324 289 -288 289 -288 289 -324 287 -288 289 -288 287 -324 287 -288 287 -286 287 -306 277 -320 273 -318 285 -288 287 -326 287 -290 287 -290 287 -326 289 -288 289 -288 289 -324 287 -288 287 -286 287 -306 277 -320 271 -318 285 -288 287 -326 287 -288 289 -288 325 -288 289 -288 287 -288 287 -324 287 -288 285 -306 277 -318 269 -318 285 -286 287 -326 289 -288 289 -288 289 -324 289 -290 287 -290 287 -324 287 -288 287 -288 285 -306 279 -318 273 -318 285 -288 285 -326 289 -288 289 -288 289 -324 289 -290 287 -288 289 -324 287 -286 287 -322 269 -280 285 -304 317 -286 285 -324 289 -288 289 -288 289 -324 253 -324 289 -290 287 -324 251 -324 287 -288 285 -322 271 -278 287 -342 281 -286 287 -324 289 -288 289 -288 289 -326 251 -326 287 -288 289 -324 287 -286 287 -286 285 -306 277 -322 271 -318 285 -286 287 -326 287 -290 287 -290 289 -324 289 -288 289 -288 289 -324 287 -288 287 -286 287 -306 277 -320 273 -318 285 -288 287 -324 289 -288 289 -288 289 -326 287 -290 287 -290 287 -324 287 -288 287 -286 305 -280 285 -304 315 -284 287 -324 287 -290 287 -290 287 -326 251 -326 289 -288 289 -324 251 -324 287 -288 285 -322 271 -278 287 -340 283 -286 285 -326 287 -290 289 -288 289 -324 253 -324 289 -288 289 -324 251 -324 287 -286 285 -308 277 -320 271 -316 285 -288 287 -324 289 -290 287 -290 287 -326 287 -290 289 -288 289 -324 287 -288 287 -288 287 -306 277 -318 271 -318 285 -288 287 -326 287 -290 287 -290 289 -324 289 -288 289 -288 289 -324 287 -288 287 -286 321 -272 279 -320 271 -316 285 -286 289 -324 289 -290 287 -290 287 -326 287 -290 289 -570 577 -630 549 -608 589 -290 285 -302 315 -286 285 -586 313 -286 287 -324 563 -290 285 -302 313 -584 281 -320 571 -294 279 -318 273 -314 287 -286 323 -288 289 -288 287 -326 251 -602 587 -582 601 -584 279 -322 571 -292 279 -322 275 -622 575 -312 283 -288
|
||||
RAW_Data: 285 -322 271 -278 287 -340 281 -584 281 -320 571 -584 589 -310 285 -580 281 -312 569 -608 585 -314 285 -600 579 -300 273 -584 315 -278 601 -586 311 -284 573 -300 277 -606 309 -284 583 -314 285 -288 287 -600 259 -314 597 -584 313 -286 567 -616 265 -308 581 -316 275 -600 293 -298 275 -320 281 -278 311 -280 287 -342 283 -282 275 -314 279 -322 571 -608 575 -290 285 -300 315 -582 279 -282 311 -284 599 -316 243 -109036 131 -1680 129 -98 65 -1024 131 -232 99 -134 99 -136 563 -166 167 -200 631 -266 163 -66 1979 -12192 133 -332 165 -200 131 -498 167 -68 99 -566 165 -398 163 -198 99 -166 199 -264 501 -166 65 -3860 97 -300 165 -234 165 -988 97 -764 65 -528 65 -98 461 -230 197 -98 229 -132 65 -196 821 -66 755 -66 6493 -11794 65 -1894 231 -268 565 -132 299 -366 97 -100 557 -66 753 -132 129 -66 15437 -10572 65 -1494 99 -198 195 -130 99 -132 131 -498 295 -1522 65 -962 133 -168 763 -366 265 -198 719 -100 163 -66 65 -134 85113 -12234 65 -532 131 -2214 197 -398 231 -132 165 -100 133 -234 131 -234 231 -66 365 -266 363 -66 395 -100 9771 -12520 129 -198 161 -1150 97 -198 163 -228 65 -1054 897 -362 131 -198 163 -298 653 -66 1417 -100 4187 -11770 165 -232 99 -534 99 -266 97 -366 65 -462 65 -500 429 -330 131 -426 97 -298 395 -362 10549 -11036 67 -1130 265 -2122 199 -266 65 -266 165 -966 197 -132 299 -598 231 -236 265 -68 197 -200 25695 -13160 99 -1154 65 -300 67 -134 67 -432 165 -656 427 -132 161 -132 361 -196 1781 -166 49797 -2762 345 -288 289 -292 325 -254 307 -274 319 -274 321 -290 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -290 307 -274 317 -274 323 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 327 -256 325 -256 327 -290 291 -290 291 -288 323 -242 305 -284 305 -320 287 -290 289 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -288 307 -276 315 -274 321 -286 327 -252 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 287 -290 307 -274 317 -274 319 -290 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292
|
||||
RAW_Data: 291 -290 291 -290 327 -254 327 -292 289 -292 291 -290 291 -288 325 -272 273 -286 341 -286 287 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -256 325 -292 289 -292 287 -290 323 -272 275 -284 341 -284 289 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 287 -290 307 -276 317 -274 321 -288 289 -290 325 -254 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -290 287 -290 307 -274 319 -274 321 -288 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -290 323 -272 275 -318 309 -286 287 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 287 -290 321 -274 275 -284 307 -320 287 -290 289 -292 325 -256 327 -290 291 -290 291 -292 289 -602 559 -612 595 -586 575 -312 289 -292 289 -290 289 -594 291 -300 313 -288 593 -284 289 -290 327 -540 309 -288 593 -294 299 -280 321 -290 289 -292 291 -290 327 -254 327 -292 289 -584 313 -286 289 -288 323 -242 597 -604 581 -590 589 -580 601 -588 281 -326 291 -290 573 -294 307 -586 283 -290 585 -316 289 -570 611 -562 317 -288 289 -292 327 -290 549 -314 289 -290 327 -292 289 -292 289 -564 613 -276 319 -290 287 -292 289 -598 575 -586 291 -286 305 -320 587 -282 327 -252 327 -540 613 -588 311 -288 585 -590 277 -290 327 -290 291 -290 595 -580 575 -588 315 -288 291 -290 603 -258 309 -598 579 -604 581 -294 299 -280 321 -560 319 -290 289 -292 577 -312 287 -109942 131 -2482 129 -100 525 -100 133 -100 133 -232 299 -200 1465 -12628 133 -332 165 -332 197 -494 131 -1644 65 -264 625 -298 99 -134 131 -132 65 -130 229 -198 557 -16188 131 -230 65 -100 97 -2200 65 -266 99 -100 97 -166 331 -200 197 -198 165 -134 693 -16662 65 -66 229 -328 463 -164 163 -98 65 -66 329 -98 557 -100 661 -14970 65 -1290 589 -460 325 -98 1087 -298 4243 -12110 131 -600 133 -334 131 -2154 199 -66 399 -66 199 -98 367 -200 329 -166 533 -16212 65 -200 167 -166 331 -100 165 -168 265 -100 131 -100 301 -398 729 -200 363 -134 65 -232 933 -12088 67 -364 133 -590 65 -1912 65 -1252
|
||||
RAW_Data: 393 -230 197 -98 99 -232 131 -294 131 -294 1117 -98 30241 -10434 395 -232 299 -100 101 -100 65 -2092 97 -2942 131 -132 97 -166 99 -398 297 -132 229 -132 659 -200 429 -100 89595 -9852 67 -3042 97 -464 267 -496 97 -362 163 -262 133 -328 263 -66 393 -132 329 -132 65 -166 859 -66 44301 -12810 99 -654 163 -1482 63 -1252 593 -266 163 -130 395 -166 229 -264 229 -130 131 -98 295 -98 1051 -66 18839 -12278 131 -1130 99 -1620 199 -268 131 -366 99 -198 199 -200 99 -66 2043 -10330 97 -200 65 -898 65 -1214 129 -884 65 -394 725 -68 163 -296 131 -164 201 -134 831 -19442 299 -134 99 -300 163 -166 97 -68 165 -66 99 -200 165 -100 497 -66 365 -66 99 -634 133 -632 99 -528 97 -2422 197 -396 99 -464 99 -432 97 -166 531 -98 299 -98 499 -166 559 -304 277 -318 245 -352 247 -352 245 -354 245 -316 237 -346 259 -342 243 -354 245 -322 283 -318 249 -318 283 -320 247 -356 237 -314 285 -302 279 -318 285 -320 287 -286 287 -326 253 -324 289 -288 289 -288 289 -326 287 -290 287 -290 287 -322 271 -278 319 -276 317 -288 289 -288 291 -328 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 291 -290 291 -288 291 -288 325 -288 289 -288 287 -278 307 -284 301 -316 285 -288 289 -328 289 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -290 289 -326 287 -290 287 -290 287 -288 307 -276 317 -274 319 -286 287 -290 327 -288 291 -288 291 -288 291 -290 325 -290 289 -290 291 -288 289 -288 325 -288 289 -288 289 -288 307 -276 285 -304 319 -286 287 -290 289 -328 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -290 291 -292 289 -290 287 -326 287 -290 287 -288 307 -278 317 -270 319 -286 289 -288 291 -326 291 -288 291 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -290 287 -326 289 -288 289 -288 289 -288 305 -276 317 -274 319 -286 287 -290 289 -328 291 -290 291 -292 289 -292 289 -290 327 -288 291 -288 291 -290 289 -290 323 -290 287 -290 287 -288 307 -276 283 -306 317 -288 287 -288 291 -326 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 289 -288 325 -288 289 -288 289 -288 323 -272 277 -320 273 -318 287 -286 291 -324 291 -290 289 -290 289 -290 291 -326 291 -288 291 -292 289 -292 289 -290 287 -326 289 -288 289 -288 287 -308 275 -318 273 -318 287 -288 289 -326 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -288 325 -288
|
||||
RAW_Data: 289 -288 289 -288 321 -272 277 -318 273 -318 287 -288 287 -328 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 287 -326 287 -290 287 -290 287 -288 307 -276 319 -272 319 -286 289 -288 327 -288 291 -288 291 -290 289 -290 325 -290 291 -288 291 -288 289 -288 325 -290 287 -290 287 -288 307 -276 285 -306 317 -286 287 -290 325 -290 289 -562 599 -580 589 -614 589 -278 285 -326 289 -288 289 -594 289 -294 275 -322 581 -284 323 -290 289 -572 307 -272 601 -312 285 -288 287 -290 325 -292 291 -290 291 -290 291 -292 289 -292 289 -604 309 -286 287 -288 593 -292 297 -312 285 -590 283 -288 575 -618 259 -324 277 -320 279 -278 311 -280 613 -280 325 -288 291 -288 291 -290 289 -292 289 -328 291 -290 291 -572 581 -590 313 -284 289 -290 585 -590 309 -286 593 -586 311 -288 287 -288 585 -590 307 -288 595 -586 575 -588 313 -288 287 -326 287 -290 563 -290 321 -576 317 -290 287 -292 291 -290 291 -290 327 -290 289 -290 571 -294 277 -322 311 -550 315 -286 289 -326 573 -582 585 -314 285 -288 287 -326 291 -290 291 -578 275 -322 289 -288 593 -294 295 -104242 133 -524 133 -592 65 -626 97 -198 97 -394 65 -362 163 -98 295 -196 131 -262 495 -166 667 -68 39523 -13350 97 -458 65 -694 131 -66 65 -462 99 -330 65 -430 133 -200 131 -68 199 -66 199 -602 233 -132 131 -302 16011 -12348 131 -432 131 -234 99 -1600 867 -166 199 -268 363 -298 1353 -66 433 -100 333 -98 4373 -12208 231 -834 131 -1524 67 -66 131 -464 99 -400 133 -300 197 -100 65 -232 1397 -68 431 -66 31401 -12444 99 -1126 99 -596 133 -570 133 -1094 97 -66 65 -66 195 -166 163 -296 825 -66 165 -196 1323 -68 529 -68 1881 -10100 63 -1988 99 -1028 197 -302 131 -402 199 -1154 65 -300 995 -66 1627 -100 15937 -14914 97 -1554 99 -234 297 -98 165 -264 65 -596 233 -600 461 -66 827 -12956 97 -760 227 -560 99 -132 163 -590 99 -466 229 -132 327 -494 97 -528 67 -66 1487 -12684 133 -332 97 -460 65 -626 65 -1976 131 -100 229 -98 195 -132 131 -66 197 -132 131 -64 725 -66 6927 -4212 65 -1064 65 -568 231 -632 101 -962 955 -64 131 -198 849 -98 129 -132 457 -16616 99 -1694 131 -698 165 -930 433 -234 1619 -100 297 -98 65545 -12050 99 -1442 65 -862 65 -1894 65 -300 67 -232 329 -98 131 -328 99 -428 659 -66 163 -166 163 -134 14483 -12870 99 -630 67 -1524 131 -100 199 -896 661 -66 199 -98 99 -136 133 -132 335 -100 759 -12342 65 -332
|
||||
RAW_Data: 785 -256 289 -288 285 -322 285 -286 285 -322 285 -286 283 -306 279 -318 271 -316 285 -322 251 -326 287 -288 289 -324 253 -324 289 -290 287 -290 287 -324 287 -288 285 -322 271 -278 285 -340 281 -286 285 -324 287 -288 287 -288 287 -324 289 -288 287 -288 287 -326 285 -288 285 -322 271 -278 287 -340 281 -286 287 -324 289 -288 287 -290 287 -326 287 -290 287 -288 287 -324 287 -288 287 -286 285 -306 279 -320 271 -318 285 -322 251 -324 289 -288 287 -324 287 -290 287 -288 287 -324 287 -286 287 -320 239 -310 285 -338 281 -286 285 -324 289 -288 287 -288 287 -324 287 -288 287 -324 253 -324 285 -288 285 -322 269 -280 287 -340 283 -284 287 -324 287 -290 287 -290 287 -326 287 -290 287 -288 287 -324 285 -286 285 -322 271 -278 287 -340 283 -284 287 -324 287 -290 287 -290 287 -324 287 -288 287 -288 287 -324 287 -288 285 -322 241 -308 285 -340 281 -284 287 -324 287 -290 287 -290 287 -326 251 -326 289 -288 287 -324 287 -288 285 -286 285 -308 279 -320 273 -318 283 -286 289 -324 289 -290 287 -290 287 -324 289 -288 287 -288 287 -322 287 -288 287 -322 269 -278 287 -340 283 -284 287 -324 287 -290 287 -290 287 -326 251 -326 289 -288 287 -324 287 -288 285 -288 283 -308 277 -322 271 -318 285 -320 253 -324 287 -288 289 -324 251 -326 287 -290 287 -324 251 -324 287 -286 285 -306 277 -320 271 -318 283 -322 251 -326 287 -290 287 -326 251 -324 287 -288 287 -324 251 -324 287 -286 285 -306 279 -318 273 -316 285 -324 287 -288 287 -288 287 -324 251 -324 287 -288 289 -324 287 -286 287 -286 285 -306 279 -320 273 -318 283 -324 251 -324 289 -288 287 -326 251 -324 287 -288 289 -322 251 -324 285 -288 285 -306 277 -320 273 -318 283 -324 251 -324 287 -288 289 -324 287 -290 287 -288 287 -324 285 -288 287 -286 275 -308 285 -336 281 -286 285 -324 289 -288 289 -288 287 -326 287 -288 287 -288 287 -324 287 -288 287 -320 241 -308 287 -338 281 -286 285 -324 289 -288 289 -288 287 -324 287 -288 287 -288 287 -324 285 -288 283 -308 279 -318 271 -316 283 -592 571 -586 583 -608 575 -314 283 -324 287 -286 287 -594 293 -296 275 -316 585 -316 249 -324 287 -570 291 -312 589 -318 285 -288 287 -288 289 -324 287 -288 289 -288 287 -322 287 -596 547 -610 575 -314 285 -600 579 -600 281 -322 287 -286 579 -590 289 -288 299 -316 285 -286 583 -620 551 -322 259 -608 315 -288 287 -288 289 -326 571 -308 269 -312 291 -576 577 -312 285 -322
|
||||
RAW_Data: 287 -564 583 -306 315 -586 575 -592 581 -302 277 -320 281 -602 577 -600 281 -282 309 -280 281 -324 575 -608 573 -292 287 -304 315 -280 277 -604 589 -608 577 -302 277 -602 273 -318 251 -308 281 -312 581 -314 281 -310 281 -604 575 -316 285 -288 287 -598 307 -286 285 -322 557 -296 301 -106188 295 -262 161 -6204 99 -2862 263 -66 163 -494 361 -462 65 -230 97 -66 1945 -9890 65 -2686 65 -3056 99 -530 65 -462 431 -98 233 -134 199 -132 99 -234 65 -100 44553 -12036 99 -1348 65 -394 263 -66 197 -198 97 -130 163 -232 263 -196 97 -66 917 -134 267 -66 1845 -4870 65 -9144 131 -1424 65 -326 261 -526 131 -796 133 -266 165 -166 165 -13352 99 -468 131 -734 65 -1094 797 -134 263 -200 431 -266 559 -16234 131 -164 65 -658 97 -1888 195 -98 391 -68 231 -132 299 -166 163 -334 62293 -12520 133 -198 333 -1092 65 -300 131 -1234 231 -68 197 -66 691 -166 527 -134 133 -134 363 -66 133 -66 565 -66 14905 -12404 297 -362 97 -1254 129 -1020 129 -294 359 -558 329 -166 497 -66 467 -164 363 -66 195 -164 15463 -13394 99 -300 165 -1426 131 -894 131 -100 229 -130 265 -134 131 -202 267 -100 199 -100 2325 -12592 65 -796 99 -266 65 -802 99 -1814 65 -300 231 -134 459 -98 161 -294 227 -228 1083 -66 25423 -16652 65 -132 99 -66 99 -168 197 -404 99 -166 661 -164 231 -100 825 -98 4801 -12388 197 -198 163 -726 163 -1486 65 -366 365 -100 599 -232 99 -100 397 -166 65 -100 931 -200 22923 -12328 131 -2028 65 -2180 165 -294 461 -66 195 -132 231 -100 99 -66 199 -66 959 -166 5897 -10540 67 -1826 133 -200 97 -1296 65 -562 99 -656 393 -132 197 -196 63 -394 163 -64 163 -164 195 -200 165 -200 1759 -278 283 -320 285 -322 251 -326 287 -288 287 -324 251 -326 289 -288 287 -324 287 -288 285 -288 275 -308 283 -338 281 -286 285 -324 287 -290 289 -288 287 -326 287 -288 289 -288 287 -322 287 -288 285 -306 279 -284 299 -316 285 -286 287 -326 287 -290 287 -290 287 -326 289 -288 287 -288 287 -324 287 -288 287 -322 239 -308 285 -340 281 -286 285 -324 289 -288 287 -290 287 -326 289 -288 287 -288 287 -324 287 -288 285 -322 269 -278 285 -340 283 -284 285 -324 289 -288 287 -288 287 -324 287 -288 287 -324 251 -322 287 -286 285 -306 279 -318 271 -316 285 -322 251 -326 287 -288 287 -326 251 -324 287 -288 287 -324 287 -288 285 -322 239 -310 283 -338 281 -284 287 -322 289 -288 287 -288 287 -324 289 -288 287 -324 251 -324 287 -286 285 -320 271 -278
|
||||
RAW_Data: 289 -342 283 -284 285 -324 287 -288 289 -288 287 -324 287 -288 287 -324 251 -324 287 -286 285 -306 277 -320 271 -316 283 -324 251 -324 289 -288 287 -326 251 -324 289 -286 287 -324 285 -286 287 -320 241 -308 287 -338 281 -284 287 -322 289 -288 287 -288 287 -324 287 -288 287 -324 251 -322 287 -286 285 -306 279 -318 273 -316 285 -322 251 -324 287 -288 287 -324 251 -324 287 -288 287 -324 285 -288 285 -320 269 -280 289 -340 283 -284 287 -322 289 -288 287 -288 287 -324 287 -288 287 -324 251 -322 287 -288 285 -320 271 -278 289 -342 281 -286 287 -324 287 -288 287 -290 287 -326 289 -288 287 -288 287 -324 285 -288 285 -322 269 -280 285 -340 281 -284 287 -324 287 -288 287 -288 287 -326 287 -288 287 -324 251 -324 287 -286 285 -320 271 -310 257 -342 283 -284 285 -324 287 -288 289 -324 251 -324 287 -288 287 -324 251 -322 287 -286 285 -306 277 -320 273 -316 285 -322 251 -324 289 -288 287 -324 287 -288 287 -286 287 -324 287 -288 285 -322 269 -280 285 -340 281 -284 285 -324 287 -290 287 -288 287 -326 287 -288 287 -324 251 -322 287 -286 285 -306 279 -318 271 -318 283 -322 251 -326 287 -288 289 -324 253 -324 287 -286 287 -324 285 -288 285 -320 269 -278 287 -340 281 -284 287 -322 289 -594 563 -582 591 -610 577 -310 283 -288 285 -306 277 -616 281 -320 287 -288 579 -312 283 -286 285 -594 293 -300 577 -316 285 -324 251 -322 289 -286 287 -324 285 -288 285 -320 239 -310 285 -340 281 -284 287 -592 279 -322 287 -288 567 -322 281 -598 577 -620 551 -324 259 -342 281 -588 281 -322 287 -288 287 -288 569 -612 301 -312 545 -610 589 -290 285 -298 315 -284 279 -312 281 -310 291 -308 283 -574 575 -322 293 -576 583 -620 273 -320 559 -294 299 -580 575 -620 271 -306 569 -342 281 -284 287 -324 287 -288 289 -286 289 -322 289 -570 293 -310 553 -314 285 -594 313 -286 287 -322 563 -292 285 -302 313 -588 281 -320 287 -288 571 -294 279 -322 273 -320 283 -592 279 -320 287 -288 567 -324 281 -7940 301 -278 321 -250 335 -282 277 -286 343 -284 279 -276 311 -278 317 -276 321 -278 275 -312 277 -320 309 -288 277 -276 311 -276 319 -276 321 -280 273 -312 277 -320 311 -286 279 -276 309 -278 321 -276 321 -278 275 -312 277 -320 309 -288 277 -278 309 -280 321 -276 321 -250 335 -282 277 -320 309 -286 277 -278 309 -278 319 -274 321 -280 273 -312 277 -320 309 -288 279 -274 311 -278 319 -276 321 -280 275 -312 277 -320 309 -288
|
||||
RAW_Data: 279 -274 311 -278 319 -276 321 -280 273 -312 307 -290 311 -286 277 -276 311 -280 321 -276 321 -250 303 -312 277 -320 309 -286 279 -276 311 -278 317 -276 319 -280 275 -310 277 -322 311 -286 279 -276 309 -278 321 -276 319 -280 275 -310 277 -322 309 -288 277 -276 311 -278 319 -276 321 -280 305 -282 307 -290 311 -286 279 -276 309 -280 321 -276 321 -250 305 -312 277 -318 309 -286 277 -276 311 -278 317 -276 321 -280 275 -310 277 -322 309 -286 279 -276 311 -278 319 -276 321 -280 275 -312 277 -318 311 -286 279 -274 311 -278 319 -276 321 -282 305 -282 307 -290 311 -284 279 -276 309 -280 321 -276 319 -250 305 -312 277 -318 311 -286 277 -276 311 -276 319 -276 321 -280 273 -312 277 -322 309 -288 277 -276 311 -278 319 -276 321 -278 277 -310 277 -320 311 -286 279 -276 309 -280 319 -276 321 -282 273 -312 307 -292 311 -284 277 -278 309 -278 319 -276 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 317 -274 321 -288 289 -290 325 -254 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 287 -290 307 -278 317 -274 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 317 -274 319 -290 287 -292 289 -292 327 -290 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -564 577 -604 585 -572 615 -258 323 -276 321 -288 289 -596 283 -288 327 -292 573 -274 307 -276 319 -576 317 -288 581 -278 323 -290 291 -288 289 -288 309 -276 315 -274 321 -286 289 -288 327 -256 325 -292 291 -580 277 -324 289 -292 563 -292 321 -576 579 -602 579 -294 301 -316 285 -560 319 -286 327 -254 325 -292 573 -582 293 -300 579 -580 601 -290 285 -304 317 -250 305 -312 277 -318 311 -284 277 -600 575 -304 315 -550 609 -572 289 -288 605 -320 287 -566 599 -580 293 -302 583 -318 289 -290 291 -292 289 -292 327 -290 291 -290 291 -572 309 -272 601 -312 287 -558 319 -288 291 -290 611 -276 321 -272 275 -618 283 -288 327 -292 547 -314 287 -290 327 -290 291 -562 289 -290 341 -284 585 -286 289 -109648 163 -164 197 -100 199 -596 1097 -168 729 -66 32021 -13854 65 -500 427 -134 633 -666 495 -230 133 -298
|
||||
RAW_Data: 99 -66 563 -132 1427 -68 365 -66 5019 -12444 129 -98 63 -1712 131 -264 65 -790 131 -100 65 -466 231 -12068 99 -200 133 -662 99 -264 99 -1160 65 -98 97 -298 99 -98 1939 -66 329 -66 233 -166 859 -14398 99 -1962 199 -396 65 -666 165 -598 99 -68 1053 -132 69649 -13640 231 -2816 261 -100 163 -596 131 -166 303 -330 10335 -13690 65 -332 167 -1026 129 -694 1247 -276 279 -320 287 -286 289 -288 325 -290 289 -290 291 -288 291 -288 327 -292 289 -292 289 -292 291 -288 289 -324 289 -288 289 -288 287 -324 271 -278 319 -274 319 -288 287 -288 291 -326 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -288 325 -288 289 -288 287 -288 305 -278 319 -272 317 -288 287 -290 287 -328 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -290 289 -290 323 -290 287 -290 287 -288 277 -308 283 -304 317 -288 287 -290 325 -292 291 -288 291 -288 291 -288 327 -290 289 -290 289 -290 291 -288 289 -324 289 -288 289 -288 287 -324 271 -278 319 -274 319 -286 289 -288 327 -290 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 289 -288 325 -288 289 -288 287 -288 305 -280 317 -274 319 -286 287 -288 327 -288 291 -288 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -290 287 -326 287 -290 287 -288 287 -308 275 -320 271 -320 285 -290 287 -328 289 -292 291 -290 291 -290 291 -292 289 -292 325 -290 291 -288 291 -288 289 -288 325 -290 287 -288 287 -288 307 -276 317 -274 317 -288 287 -290 323 -292 291 -290 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 287 -326 287 -290 287 -290 287 -324 269 -278 317 -274 319 -286 289 -288 327 -288 291 -288 291 -288 291 -290 325 -290 289 -292 291 -290 291 -290 289 -324 289 -290 287 -290 287 -322 271 -278 317 -274 319 -286 289 -288 327 -290 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 289 -290 291 -288 325 -288 289 -288 287 -288 305 -278 319 -272 319 -286 287 -290 289 -326 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -290 287 -326 287 -290 289 -288 287 -324 271 -276 317 -274 319 -288 287 -288 327 -290 291 -292 289 -290 289 -290 327 -288 291 -288 291 -290 289 -290 287 -326 287 -290 287 -290 287 -322 271 -278 319 -276 319 -286 287 -290 289 -328 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -292 287 -290 325 -288 289 -564 613 -578 579 -600
|
||||
RAW_Data: 581 -296 299 -314 285 -286 289 -594 281 -324 289 -288 573 -292 279 -322 309 -552 315 -286 577 -310 285 -306 277 -318 273 -320 285 -288 289 -328 289 -292 289 -292 291 -600 277 -288 287 -326 287 -290 563 -292 323 -274 319 -286 287 -596 573 -310 285 -596 585 -312 285 -592 283 -286 577 -618 291 -294 575 -318 287 -290 291 -290 291 -578 309 -286 307 -276 613 -282 287 -326 291 -580 277 -322 563 -584 601 -576 603 -576 601 -580 279 -312 599 -276 317 -558 317 -286 579 -586 615 -274 317 -286 287 -594 573 -310 285 -594 291 -298 311 -288 285 -288 289 -328 289 -292 289 -292 291 -290 291 -290 291 -328 541 -308 305 -278 317 -272 319 -590 283 -286 325 -290 573 -310 269 -8012 315 -288 287 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -256 325 -292 287 -290 287 -288 307 -276 317 -272 321 -288 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -290 323 -242 307 -286 339 -284 289 -288 325 -254 327 -288 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 287 -290 307 -276 315 -274 319 -290 287 -292 289 -292 327 -290 291 -292 289 -292 289 -292 327 -254 327 -254 327 -292 289 -292 291 -290 291 -290 327 -254 327 -292 289 -292 291 -290 289 -288 325 -272 275 -318 309 -286 289 -288 291 -290 327 -256 325 -292 291 -290 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 289 -288 323 -242 305 -284 305 -318 287 -290 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 289 -290 325 -242 305 -284 339 -286 287 -288 327 -252 327 -288 291 -292 289 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 287 -290 307 -278 317 -274 321 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -288 289 -288 307 -276 317 -274 319 -288 289 -290 291 -292 325 -292 291 -290 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 289 -288 287 -278 307 -284 303 -318 287 -288 291 -290 291 -290 327 -292 289 -292
|
||||
RAW_Data: 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 325 -292 291 -290 291 -290 291 -290 289 -288 309 -274 317 -274 321 -288 289 -290 325 -254 325 -290 291 -290 291 -288 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -290 289 -288 309 -564 607 -586 605 -582 583 -312 287 -290 289 -292 325 -552 317 -288 291 -290 603 -260 311 -294 313 -556 319 -290 599 -274 275 -308 283 -304 319 -278 275 -312 277 -320 309 -286 279 -276 311 -598 309 -286 277 -278 309 -278 617 -284 323 -292 291 -290 291 -582 591 -288 281 -600 587 -316 289 -572 307 -272 601 -580 317 -290 581 -276 323 -292 289 -290 289 -594 291 -298 315 -286 585 -286 289 -290 327 -568 311 -288 563 -580 607 -586 605 -582 583 -582 317 -290 581 -276 323 -564 289 -288 605 -586 599 -286 277 -320 309 -556 613 -280 323 -562 289 -286 303 -318 287 -288 289 -290 327 -254 327 -290 291 -292 289 -292 289 -292 577 -310 321 -242 307 -286 305 -588 319 -288 291 -290 579 -312 287 -109360 133 -862 65 -996 65 -1096 65 -332 399 -300 131 -330 961 -15908 99 -532 163 -332 167 -366 133 -264 131 -600 431 -264 131 -300 99 -398 265 -134 99 -100 1661 -68 133 -100 101033 -15636 65 -626 163 -230 327 -98 99 -362 97 -132 591 -164 15257 -13050 99 -334 167 -100 67 -232 99 -166 131 -66 99 -1884 297 -164 263 -164 229 -230 199 -230 97 -494 1379 -130 1355 -68 27761 -12984 163 -392 97 -200 199 -100 233 -500 267 -234 133 -166 269 -232 265 -98 59889 -13068 99 -428 131 -364 65 -2604 163 -66 297 -296 229 -196 131 -716 625 -228 66595 -12098 65 -896 197 -398 163 -362 65 -68 131 -398 459 -132 529 -66 165 -100 397 -134 133 -134 1783 -66 20355 -12838 97 -166 65 -896 63 -2688 199 -432 199 -564 165 -100 131 -64 163 -130 197 -130 361 -132 423 -132 97 -66 24621 -12272 65 -298 229 -1192 627 -528 199 -264 295 -12128 65 -398 99 -4244 333 -66 365 -232 99 -66 99 -596 195 -98 921 -494 3003 -314 199 -380 207 -384 209 -384 207 -384 209 -368 221 -386 201 -380 209 -386 209 -350 243 -350 245 -350 243 -336 221 -356 269 -350 243 -318 245 -352 247 -352 247 -352 245 -316 237 -346 257 -340 245 -352 249 -320 283 -320 247 -320 283 -318 249 -354 247 -306 279 -322 271 -316 283 -320 287 -288 287 -288 289 -324 287 -290 287 -290 287 -326 289 -288 289 -288 289 -322 271 -278 317 -274 319 -286 289 -288 327 -290 291 -292 289 -292 289 -292 291 -290
|
||||
RAW_Data: 291 -328 289 -292 289 -292 291 -290 291 -290 291 -324 289 -290 287 -288 287 -308 275 -288 339 -282 287 -288 289 -290 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 323 -290 289 -288 289 -288 287 -308 275 -320 273 -320 285 -290 287 -328 289 -292 291 -288 291 -288 291 -288 327 -290 289 -290 289 -290 289 -288 325 -288 289 -288 287 -288 307 -278 283 -306 319 -286 289 -288 289 -326 291 -290 291 -292 289 -292 289 -292 291 -326 291 -292 289 -292 289 -292 291 -290 289 -324 289 -288 289 -288 287 -308 275 -320 273 -318 287 -286 325 -290 291 -288 291 -288 291 -288 291 -326 289 -292 291 -290 291 -290 291 -288 289 -326 287 -290 287 -288 287 -308 275 -318 273 -320 287 -288 289 -326 291 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 289 -290 291 -288 325 -288 289 -288 287 -290 321 -272 277 -288 303 -318 287 -290 287 -292 325 -292 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -290 291 -292 287 -292 323 -290 287 -290 287 -288 307 -278 283 -304 319 -286 287 -290 327 -290 291 -288 291 -288 291 -290 289 -326 289 -290 291 -288 289 -288 289 -324 289 -290 287 -288 287 -308 275 -320 271 -320 287 -288 289 -326 291 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -288 325 -288 289 -288 287 -278 309 -284 303 -314 289 -286 289 -290 325 -290 289 -292 291 -290 291 -290 291 -328 289 -292 291 -290 291 -290 291 -290 289 -326 287 -290 287 -290 287 -324 269 -278 319 -274 319 -286 289 -288 325 -562 601 -582 585 -578 609 -278 321 -290 289 -288 287 -594 291 -298 311 -286 583 -284 321 -290 289 -570 309 -286 593 -290 295 -312 285 -288 287 -290 325 -292 291 -290 291 -290 291 -600 277 -288 325 -288 563 -582 605 -316 289 -290 291 -288 291 -288 327 -572 273 -308 597 -276 317 -286 289 -594 573 -310 287 -288 307 -564 605 -318 289 -562 597 -574 593 -614 285 -288 287 -328 571 -286 277 -616 283 -288 585 -604 577 -604 281 -324 549 -604 577 -304 315 -286 287 -290 287 -328 291 -562 601 -582 587 -312 285 -290 287 -598 571 -310 287 -324 271 -276 317 -274 319 -592 577 -590 287 -284 599 -318 287 -564 599 -308 271 -310 291 -578 319 -288 291 -290 581 -310 287 -97108 497 -132 99 -132 133 -98 133 -464 231 -9270 99 -528 131 -562 131 -398 65 -164 459 -166 427 -66 229 -12830 133 -66 99 -200 133 -1030 99 -828 163 -630 133 -164
|
||||
RAW_Data: 165 -298 167 -66 397 -200 431 -298 131 -134 397 -132 1169 -68 189637 -12266 331 -464 97 -300 197 -302 131 -432 163 -364 329 -66 99 -166 53667 -9988 133 -2386 133 -792 133 -1326 165 -664 65 -332 427 -232 131 -398 131 -562 1689 -100 165 -134 267 -66 6079 -11076 65 -1030 97 -460 65 -566 199 -664 197 -630 131 -464 233 -198 99 -166 65 -134 633 -66 167 -300 165 -166 827 -66 65909 -11540 65 -1032 99 -366 99 -198 65 -490 755 -298 359 -66 525 -298 825 -100 21227 -10636 65 -1492 99 -598 131 -1062 133 -1030 199 -98 163 -134 99 -200 231 -100 399 -134 695 -102 26705 -12940 65 -234 165 -1818 197 -560 129 -132 131 -330 299 -134 497 -134 335 -198 229 -98 41297 -264 307 -292 313 -286 289 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 325 -256 327 -254 327 -290 291 -290 289 -288 287 -290 307 -276 317 -274 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -290 323 -242 305 -286 341 -282 287 -288 327 -252 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 289 -292 289 -290 289 -288 307 -278 315 -274 319 -288 287 -292 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -288 289 -288 309 -276 315 -276 319 -286 289 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -290 289 -288 287 -278 307 -282 303 -318 287 -290 289 -292 325 -292 291 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 287 -310 275 -284 341 -284 287 -288 291 -288 327 -288 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -288 289 -288 307 -276 319 -274 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 327 -254 327 -288 291 -292 289 -292 289 -292 327 -254 327 -290 291 -292 289 -290 287 -290 323 -272 275 -318 309 -286 287 -288 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -288 289 -288 323 -272 275 -288 339 -284 287 -288 291 -290 327 -254 327 -292 289 -292 289 -292 291 -290
|
||||
RAW_Data: 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -288 289 -288 309 -276 315 -272 319 -288 291 -288 327 -252 327 -292 289 -290 289 -292 327 -252 327 -290 291 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 289 -290 287 -290 307 -564 607 -588 601 -572 589 -312 287 -290 289 -292 325 -550 313 -288 325 -292 563 -290 317 -270 317 -558 319 -288 581 -310 287 -290 287 -290 307 -278 317 -274 319 -288 289 -290 291 -586 605 -274 307 -276 319 -274 319 -288 289 -596 281 -290 327 -290 291 -290 573 -578 591 -584 317 -290 577 -312 287 -278 307 -284 301 -316 287 -290 289 -598 573 -312 287 -594 289 -294 577 -320 287 -292 289 -600 561 -292 309 -592 579 -318 289 -572 577 -296 305 -318 285 -288 291 -584 317 -290 289 -292 289 -292 289 -292 327 -290 565 -290 283 -604 579 -316 289 -290 291 -570 321 -282 297 -316 553 -612 591 -288 281 -596 319 -288 583 -278 321 -564 609 -274 317 -288 287 -586 317 -288 291 -292 571 -294 309 -8068 311 -290 289 -290 323 -272 275 -318 309 -286 287 -288 291 -290 327 -292 289 -292 291 -290 291 -290 291 -290 291 -292 327 -290 291 -290 291 -290 291 -292 325 -254 325 -290 289 -290 287 -288 307 -278 283 -306 317 -288 289 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -288 289 -288 307 -278 317 -274 321 -286 289 -290 325 -256 325 -290 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -288 289 -288 309 -276 315 -272 319 -288 289 -292 289 -292 327 -290 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 315 -274 321 -288 289 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -288 287 -290 305 -278 317 -272 317 -288 289 -290 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 289 -290 289 -288 307 -276 317 -274 321 -288 289 -290 325 -256 327 -288 291 -288 291 -288 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -288 289 -290 307 -276 317 -274 319 -288 289 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290
|
||||
RAW_Data: 291 -290 291 -292 325 -256 327 -254 327 -290 291 -290 289 -288 289 -288 309 -276 317 -274 321 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 289 -288 323 -242 307 -286 341 -282 289 -288 289 -292 327 -254 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 287 -290 307 -278 317 -274 321 -288 289 -292 325 -256 327 -290 291 -288 291 -288 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -564 579 -608 579 -604 581 -294 297 -278 321 -290 287 -596 283 -290 327 -290 575 -274 307 -276 317 -578 317 -292 581 -278 323 -290 287 -290 287 -288 307 -276 315 -276 319 -288 287 -598 575 -312 285 -290 287 -290 307 -276 317 -576 319 -288 291 -292 289 -292 579 -588 613 -576 317 -290 583 -278 323 -254 327 -290 289 -288 287 -290 307 -566 605 -284 325 -564 315 -288 571 -310 271 -310 293 -580 579 -316 287 -602 577 -302 313 -554 613 -280 287 -326 289 -292 291 -564 289 -288 307 -320 287 -290 289 -292 289 -292 585 -314 289 -568 611 -300 277 -320 279 -566 319 -280 297 -318 583 -580 591 -288 281 -602 317 -290 563 -316 289 -570 613 -264 307 -318 287 -572 285 -308 279 -300 615 -284 275 -105118 65 -1490 131 -164 229 -460 129 -1582 597 -68 231 -132 425 -264 229 -100 753 -100 163 -98 85631 -12124 65 -722 193 -430 65 -1348 65 -164 163 -826 229 -230 985 -232 1049 -15274 67 -598 133 -1914 99 -632 65 -234 99 -198 229 -64 65 -100 295 -100 99 -200 1191 -15222 63 -1330 65 -300 65 -132 165 -100 165 -166 101 -666 65 -98 559 -296 1087 -66 197 -16066 65 -1148 129 -100 97 -66 99 -296 363 -98 787 -230 623 -66 229 -130 35869 -12846 65 -4092 263 -132 199 -332 199 -100 497 -66 1551 -66 23883 -12188 197 -1288 165 -100 101 -634 99 -430 65 -166 65 -168 99 -498 195 -100 1019 -132 261 -294 1509 -164 20415 -5922 65 -7900 129 -626 163 -198 231 -96 65 -130 131 -296 561 -68 393 -66 129 -98 41103 -12648 65 -132 131 -1660 195 -590 393 -166 99 -132 599 -360 131 -98 3659 -12334 167 -298 165 -360 65 -2522 165 -270 65 -332 67 -100 97 -68 231 -396 297 -132 965 -66 589 -102 45001 -12524 131 -100 67 -832 199 -428 131 -890 65 -590 227 -364 657 -132 197 -162 623 -396 1289 -98 131 -132 133 -12706 131 -166 67 -398 163 -798 131 -268 133 -732 131 -164
|
||||
RAW_Data: 265 -66 233 -264 197 -66 163 -230 163 -230 265 -134 1125 -66 14753 -284 325 -292 291 -290 287 -290 325 -272 273 -318 309 -286 287 -288 291 -290 327 -292 289 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 289 -290 289 -288 307 -276 315 -274 319 -290 287 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -254 327 -292 289 -292 287 -290 325 -242 305 -286 339 -284 287 -288 327 -252 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 289 -290 289 -288 309 -276 315 -274 319 -288 289 -290 291 -290 327 -292 289 -292 289 -290 291 -288 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 289 -288 323 -272 275 -318 309 -286 287 -290 289 -292 327 -290 291 -292 289 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 327 -252 327 -292 287 -288 289 -288 307 -276 317 -272 319 -288 289 -290 291 -292 325 -292 291 -290 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -290 287 -290 307 -276 317 -274 321 -288 289 -288 327 -252 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -288 289 -288 309 -276 317 -274 321 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -288 289 -288 309 -276 317 -274 319 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 289 -290 287 -288 323 -272 277 -286 339 -286 287 -290 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -288 289 -288 309 -276 319 -276 319 -288 289 -290 325 -254 325 -290 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -570 611 -566 613 -590 591 -258 311 -298 315 -288 289 -586 319 -288 291 -290 571 -310 271 -312 293 -578 317 -290 581 -274 323 -290 287 -288 325 -272 277 -286 305 -320 287 -290 289 -598 283 -288 327 -290 575 -580 293 -300 581 -320 287 -584 313 -288 563 -290 285 -606 579 -590 617 -260 321 -578 317 -292 583 -278
|
||||
RAW_Data: 323 -564 579 -604 281 -290 585 -590 309 -288 595 -290 297 -580 577 -592 307 -278 593 -306 315 -288 289 -562 609 -314 287 -564 289 -288 307 -320 289 -288 587 -282 327 -574 581 -294 297 -280 321 -558 613 -590 273 -308 275 -318 309 -286 587 -286 287 -602 313 -252 599 -578 305 -316 553 -318 289 -290 291 -292 325 -568 311 -290 291 -290 597 -260 323 -106046 65 -596 327 -66 65 -890 163 -528 99 -264 129 -396 627 -64 327 -132 395 -100 97 -130 757 -232 29947 -5610 99 -960 167 -2782 99 -432 331 -398 163 -262 99 -198 595 -164 1545 -130 65 -98 561 -66 190121 -12208 65 -666 165 -2412 99 -262 65 -198 65 -526 133 -166 165 -200 199 -132 199 -334 5377 -12114 67 -334 163 -1942 231 -1494 197 -100 67 -198 367 -130 131 -100 99 -134 152657 -12576 65 -1424 131 -134 265 -166 465 -100 197 -134 131 -362 131 -66 1519 -164 10841 -12966 65 -1516 381 -326 217 -346 253 -346 225 -368 241 -350 221 -348 251 -352 233 -344 243 -352 249 -348 251 -322 269 -348 219 -346 251 -316 285 -334 243 -318 279 -316 249 -350 267 -314 251 -344 251 -312 285 -338 243 -318 279 -314 281 -316 271 -316 281 -278 281 -312 285 -306 317 -282 275 -314 277 -320 273 -318 281 -276 313 -280 319 -274 319 -280 275 -314 277 -320 273 -318 281 -276 311 -280 317 -274 319 -280 275 -312 279 -320 273 -320 281 -276 313 -278 317 -310 283 -282 275 -312 277 -320 275 -320 279 -276 313 -278 319 -274 319 -280 277 -312 279 -320 273 -320 279 -278 311 -280 319 -274 319 -280 277 -312 279 -318 271 -320 279 -276 313 -278 319 -308 285 -280 275 -312 279 -320 273 -320 281 -276 313 -278 319 -274 319 -280 275 -312 279 -318 275 -320 279 -276 313 -278 319 -274 319 -282 275 -314 277 -322 307 -282 279 -278 311 -278 321 -274 319 -280 277 -314 277 -318 273 -318 281 -276 313 -278 319 -308 285 -280 277 -312 277 -320 275 -320 279 -276 313 -278 319 -308 283 -282 275 -312 277 -322 273 -320 279 -276 313 -280 319 -274 319 -282 275 -314 277 -320 309 -282 279 -276 311 -280 321 -274 317 -282 275 -314 277 -318 275 -318 281 -276 313 -278 317 -274 319 -280 277 -312 279 -320 273 -320 279 -276 313 -280 317 -308 283 -282 275 -312 277 -322 273 -320 279 -278 311 -280 319 -274 319 -282 275 -314 277 -322 273 -318 279 -276 311 -280 317 -276 317 -280 277 -312 277 -320 273 -320 279 -278 311 -280 319 -308 283 -282 275 -312 279 -320 273 -320 279 -276 313 -280 317 -274 319 -280 275 -314 277 -320
|
||||
RAW_Data: 275 -318 281 -278 311 -278 319 -274 319 -282 275 -314 277 -320 273 -318 279 -276 311 -278 321 -310 283 -280 275 -314 279 -318 275 -320 279 -278 311 -280 317 -274 317 -282 275 -314 277 -320 273 -320 279 -276 311 -280 319 -274 319 -282 275 -314 277 -320 309 -284 277 -278 311 -280 321 -274 319 -282 273 -314 279 -318 273 -318 281 -276 311 -278 319 -276 317 -282 275 -314 277 -318 275 -318 281 -276 313 -278 319 -308 285 -280 275 -312 279 -320 273 -586 609 -592 577 -602 579 -310 275 -280 311 -296 313 -582 277 -280 309 -284 599 -318 275 -280 309 -576 311 -282 601 -290 281 -300 315 -286 281 -308 281 -278 321 -310 285 -280 277 -600 293 -302 579 -612 273 -308 277 -318 575 -316 273 -602 583 -578 579 -596 581 -594 313 -288 583 -318 285 -570 293 -310 291 -312 549 -318 285 -578 585 -616 275 -318 585 -284 285 -326 287 -290 289 -290 287 -602 289 -282 297 -316 583 -574 589 -290 287 -604 281 -326 287 -292 581 -310 287 -288 287 -324 271 -598 573 -318 287 -290 287 -292 291 -290 327 -292 289 -292 289 -292 291 -570 607 -596 581 -282 323 -288 291 -290 289 -570 609 -300 277 -318 287 -288 287 -596 281 -306 279 -310 589 -282 281 -104432 233 -2124 65 -426 229 -64 197 -66 659 -100 65 -132 65 -266 65 -298 267 -66 1737 -12050 199 -166 65 -430 99 -398 67 -1160 99 -958 99 -66 265 -66 165 -132 165 -234 793 -134 1029 -15454 129 -296 131 -726 97 -262 63 -100 131 -1628 65 -496 199 -66 595 -166 165 -68 165 -164 131 -132 561 -15816 97 -954 165 -266 65 -726 199 -166 131 -334 365 -396 2479 -66 60999 -11996 163 -392 97 -264 227 -492 265 -132 133 -632 99 -1260 65 -234 131 -168 297 -68 463 -596 99 -66 297 -132 229 -98 23489 -13944 165 -2462 65 -166 97 -262 131 -262 495 -100 10955 -13710 167 -1090 97 -698 65 -366 131 -166 99 -66 99 -296 265 -132 463 -68 165 -330 1459 -66 25519 -12104 165 -2210 65 -2360 99 -266 165 -132 763 -134 163 -100 165 -100 669 -164 197 -100 197 -100 1463 -12108 99 -800 99 -166 133 -562 165 -2176 65 -100 361 -234 2419 -66 16825 -12914 165 -398 131 -698 97 -1464 167 -698 467 -66 331 -132 297 -68 2059 -68 84375 -12248 97 -1496 99 -266 99 -400 167 -400 99 -134 131 -766 97 -66 265 -132 195 -98 727 -200 197 -230 105983 -12614 65 -232 67 -198 131 -532 163 -200 97 -364 131 -132 63 -98 165 -100 263 -234 14361 -12148 65 -662 65 -596 63 -2344 99 -98 163 -294 163 -98 231 -198 65 -332 795 -166
|
||||
RAW_Data: 197 -198 65 -100 1291 -68 5285 -12844 165 -100 133 -728 131 -230 65 -790 163 -326 791 -264 131 -326 97 -134 65 -100 131 -280 307 -274 317 -274 321 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 289 -288 309 -276 315 -274 319 -288 289 -290 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 317 -274 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 317 -274 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -290 289 -288 289 -288 307 -278 283 -306 317 -288 289 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 291 -288 289 -288 309 -276 317 -274 319 -288 289 -288 327 -252 327 -290 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 289 -290 289 -288 309 -276 315 -274 319 -288 289 -290 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -288 289 -288 309 -276 317 -274 319 -288 289 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -290 291 -288 289 -288 323 -272 277 -286 307 -318 289 -288 289 -292 327 -254 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 287 -324 271 -278 317 -276 319 -288 291 -288 327 -256 325 -290 289 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -288 289 -288 309 -276 317 -272 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 327 -254 327 -254 327 -566 577 -606 581 -590 587 -290 285 -304 317 -288 289 -596 283 -286 327 -292 573 -274 307 -276 319 -578 317 -290 581 -278 323 -290 291 -288 289 -288 307 -276 317 -274 319 -286 289 -288 327 -562 571 -584 615 -276 319 -290 287 -292 289 -292 327 -550 315 -286
|
||||
RAW_Data: 327 -252 601 -290 283 -600 587 -590 587 -292 283 -304 317 -288 287 -596 575 -312 287 -562 321 -292 577 -316 289 -292 291 -576 285 -308 607 -582 283 -288 607 -286 277 -318 309 -556 319 -286 579 -312 287 -290 287 -594 587 -314 285 -290 289 -598 281 -290 609 -582 585 -580 317 -290 289 -292 291 -290 577 -310 287 -324 271 -598 275 -320 585 -286 289 -602 277 -288 599 -290 283 -302 319 -556 319 -288 291 -290 577 -310 287 -8040 299 -280 319 -250 305 -312 277 -316 309 -286 279 -276 311 -276 319 -276 321 -278 277 -310 277 -320 311 -286 279 -276 311 -280 319 -276 321 -280 305 -282 277 -322 309 -284 277 -278 309 -280 319 -276 319 -280 275 -312 277 -318 309 -288 277 -276 311 -278 319 -276 319 -280 275 -312 277 -320 311 -286 281 -274 311 -278 319 -274 321 -280 275 -312 277 -320 309 -286 279 -276 311 -278 321 -276 321 -250 337 -282 277 -318 309 -284 279 -276 311 -278 319 -274 321 -280 275 -312 277 -320 309 -288 279 -274 311 -278 319 -276 321 -280 275 -312 277 -320 309 -286 281 -274 311 -278 319 -276 319 -280 275 -312 277 -322 309 -286 279 -276 309 -280 321 -274 321 -250 305 -314 277 -318 309 -286 277 -276 311 -278 319 -274 321 -280 275 -312 277 -320 309 -286 279 -276 311 -278 319 -276 321 -280 275 -312 277 -320 309 -286 279 -274 311 -278 321 -274 321 -280 307 -282 307 -292 309 -284 279 -276 311 -280 319 -276 319 -252 303 -312 277 -320 309 -286 279 -274 311 -278 319 -276 319 -280 277 -310 277 -320 311 -286 279 -276 311 -278 319 -276 319 -280 275 -312 277 -320 309 -288 277 -276 311 -278 319 -276 321 -282 305 -282 307 -290 311 -284 279 -276 309 -280 321 -274 321 -250 305 -312 277 -318 309 -286 279 -274 311 -278 319 -276 321 -278 277 -310 277 -320 311 -286 279 -276 311 -278 319 -276 319 -280 275 -312 277 -320 311 -286 277 -278 311 -278 319 -276 319 -282 305 -282 277 -320 311 -284 279 -276 311 -278 319 -276 319 -278 277 -312 275 -322 309 -286 279 -276 311 -278 319 -276 319 -280 275 -312 277 -320 309 -286 281 -274 311 -278 319 -276 321 -278 277 -310 277 -320 311 -286 279 -276 309 -280 319 -276 321 -250 337 -282 277 -320 307 -286 277 -276 311 -278 319 -276 319 -280 275 -312 277 -320 309 -286 279 -276 311 -278 319 -276 321 -280 275 -312 277 -320 309 -286 279 -276 311 -278 319 -276 319 -280 275 -312 277 -322 309 -286 279 -276 311 -278 321 -276 321 -250 303 -314 277 -320 309 -284 279 -276
|
||||
RAW_Data: 311 -278 319 -276 319 -280 275 -312 277 -320 309 -552 613 -560 609 -600 579 -284 307 -276 309 -294 311 -556 319 -290 289 -292 577 -310 321 -242 307 -580 315 -290 585 -318 287 -290 291 -290 291 -290 327 -292 289 -292 289 -290 287 -290 307 -564 607 -588 571 -310 287 -308 275 -318 273 -320 287 -594 283 -288 327 -292 573 -274 307 -566 607 -588 601 -274 305 -278 319 -274 319 -560 611 -276 323 -564 289 -288 603 -320 287 -292 289 -566 313 -288 603 -576 301 -278 587 -320 287 -292 289 -602 277 -288 599 -290 283 -302 319 -558 611 -272 307 -278 321 -578 317 -274 601 -584 575 -580 313 -288 323 -290 291 -290 563 -290 319 -272 319 -558 319 -288 579 -310 287 -594 291 -294 579 -318 289 -290 291 -580 309 -288 289 -288 595 -294 299 -101676 261 -434 165 -66 67 -1092 99 -232 97 -200 233 -66 131 -200 267 -166 759 -66 231 -200 997 -132 727 -100 927 -166 58605 -4898 99 -2862 99 -2162 133 -398 199 -100 131 -132 131 -530 65 -234 857 -66 299 -326 133 -134 99 -5674 97 -630 131 -132 63 -132 97 -592 691 -100 427 -66 2441 -66 235 -4078 65 -2260 65 -760 65 -2628 99 -496 99 -100 97 -528 233 -164 131 -66 1217 -166 461 -134 65 -132 97 -526 97 -132 65 -132 133 -366 99 -200 329 -330 65 -98 163 -134 165 -166 297 -66 126999 -12792 65 -232 97 -234 65 -198 99 -1098 97 -660 133 -892 295 -164 263 -164 129 -196 131 -196 329 -262 363 -66 3103 -13504 161 -528 131 -134 97 -232 193 -790 99 -818 263 -196 723 -494 1711 -100 21433 -12994 65 -3578 131 -268 231 -430 331 -198 1885 -12758 99 -2090 65 -1316 97 -132 129 -296 97 -426 65 -1494 133 -100 731 -100 99 -168 165 -100 397 -132 197 -168 1425 -100 529 -100 21545 -12232 99 -726 165 -132 165 -470 97 -232 331 -98 301 -330 165 -200 133 -300 231 -236 6597 -288 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 317 -274 319 -288 289 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -288 291 -292 289 -288 287 -290 305 -278 315 -272 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -290 289 -290 287 -278 305 -284 339 -284 289 -288 325 -254 327 -290 291 -288 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 289 -292
|
||||
RAW_Data: 327 -290 291 -292 289 -292 287 -290 287 -308 275 -286 305 -320 287 -290 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 317 -272 319 -288 289 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -290 291 -288 289 -288 323 -272 277 -286 307 -320 287 -288 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 289 -288 289 -306 275 -318 307 -286 287 -290 325 -254 327 -290 291 -288 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 287 -290 307 -276 317 -274 321 -288 289 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -290 291 -292 325 -256 327 -254 327 -290 289 -288 289 -288 325 -272 275 -318 309 -286 287 -288 291 -290 327 -292 289 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 289 -288 325 -242 305 -286 337 -284 287 -288 289 -290 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -290 287 -290 307 -276 315 -274 319 -290 287 -292 289 -598 571 -586 615 -578 615 -280 287 -290 327 -254 327 -564 289 -284 301 -320 583 -286 287 -292 327 -566 311 -288 563 -290 319 -276 319 -288 287 -292 291 -290 327 -292 289 -292 289 -292 289 -292 291 -290 327 -568 589 -290 283 -298 317 -286 289 -288 325 -548 315 -286 289 -292 601 -578 601 -586 573 -588 613 -576 319 -288 289 -292 291 -290 291 -290 577 -586 613 -276 319 -290 287 -290 327 -550 315 -288 291 -290 603 -258 311 -298 315 -286 291 -588 283 -288 609 -582 585 -580 319 -288 291 -290 291 -292 577 -584 613 -578 317 -290 291 -290 581 -310 287 -290 287 -596 585 -312 287 -592 283 -290 611 -550 321 -292 311 -286 587 -284 289 -290 327 -568 311 -288 291 -288 595 -288 293 -8030 311 -274 307 -282 297 -316 279 -276 309 -280 319 -274 321 -252 303 -312 277 -318 309 -286 279 -274 311 -278 319 -274 321 -280 275 -312 277 -320 309 -288 279 -274 311 -278 319 -276 321 -280 275 -312 277 -320 309 -288 279 -274 311 -278 319 -276 321 -282 305 -280 307 -292 311 -284 277 -278 309 -280 321 -274 321 -250
|
||||
RAW_Data: 305 -312 277 -318 309 -286 279 -276 311 -276 319 -276 321 -278 277 -310 277 -322 309 -286 279 -274 311 -278 321 -276 321 -278 275 -312 277 -320 309 -288 277 -276 311 -278 319 -276 321 -282 305 -282 307 -290 311 -284 279 -276 309 -280 321 -274 321 -280 273 -312 277 -318 309 -288 279 -274 311 -278 319 -276 321 -278 277 -310 277 -320 311 -286 279 -276 309 -278 321 -276 321 -280 273 -312 277 -320 309 -288 279 -276 309 -280 321 -276 319 -282 305 -282 277 -320 311 -284 279 -276 311 -278 317 -276 319 -280 275 -312 275 -320 309 -288 279 -274 311 -278 321 -274 321 -280 275 -312 277 -320 309 -286 279 -274 311 -278 321 -276 321 -278 275 -312 277 -320 309 -288 277 -278 309 -278 321 -276 313 -276 311 -278 317 -274 319 -250 305 -312 277 -318 309 -286 279 -274 311 -278 319 -276 319 -280 275 -312 277 -320 309 -288 279 -274 311 -278 321 -274 321 -280 275 -312 277 -320 309 -288 279 -274 311 -278 319 -276 321 -282 305 -282 277 -320 309 -286 277 -276 311 -278 319 -276 321 -280 273 -312 277 -320 309 -288 279 -274 311 -278 319 -276 321 -280 275 -312 277 -320 309 -286 279 -276 311 -278 319 -276 319 -280 275 -312 277 -320 309 -288 277 -278 309 -280 321 -274 321 -252 335 -282 307 -290 311 -284 277 -278 311 -278 317 -276 319 -280 275 -310 277 -320 311 -286 279 -276 309 -278 319 -276 321 -280 275 -312 277 -320 309 -288 277 -276 311 -278 319 -276 321 -278 275 -312 307 -290 311 -286 279 -276 309 -280 323 -274 321 -252 335 -282 277 -286 341 -284 279 -276 311 -278 317 -276 321 -280 275 -312 277 -320 309 -286 279 -274 311 -278 319 -276 321 -280 275 -312 277 -320 309 -288 277 -278 309 -278 319 -276 321 -280 275 -310 277 -618 579 -568 617 -588 577 -316 273 -278 309 -282 303 -586 319 -274 307 -278 611 -284 277 -308 279 -602 313 -286 593 -286 273 -310 277 -320 309 -288 279 -274 311 -278 319 -276 321 -280 305 -282 307 -290 311 -548 613 -270 305 -280 321 -276 313 -276 311 -564 307 -320 279 -276 597 -590 579 -612 593 -578 599 -586 319 -250 327 -292 289 -292 289 -292 569 -612 595 -282 289 -290 327 -290 291 -564 315 -290 291 -290 603 -260 309 -296 313 -286 289 -588 283 -324 573 -582 587 -580 317 -288 289 -290 325 -254 609 -584 585 -578 319 -288 291 -290 579 -312 285 -290 307 -564 605 -282 327 -544 313 -280 601 -576 319 -288 291 -292 605 -286 275 -318 309 -556 319 -288 291 -292 577 -312 285 -108734 67 -530
|
||||
RAW_Data: 133 -1292 197 -1792 65 -560 263 -296 131 -98 97 -330 889 -66 1521 -100 165 -134 136165 -12080 99 -4236 129 -230 161 -98 295 -166 201 -102 131 -166 165 -266 99 -100 1591 -66 2859 -12200 65 -230 97 -562 131 -266 165 -168 131 -536 65 -762 99 -986 921 -300 197 -462 197 -134 861 -166 329 -66 431 -66 53127 -12162 263 -330 131 -198 97 -164 99 -1384 131 -164 99 -164 263 -132 327 -230 591 -164 229 -66 8411 -13974 97 -788 65 -262 97 -1180 263 -230 131 -296 231 -298 199 -66 265 -100 99 -100 1269 -100 695 -13080 133 -266 133 -232 65 -266 65 -2160 99 -200 99 -198 297 -466 133 -396 297 -100 65 -132 165 -66 955 -13906 131 -2590 97 -626 165 -232 463 -312 241 -348 245 -350 209 -384 209 -350 245 -350 237 -348 257 -336 241 -350 247 -352 247 -316 247 -352 245 -354 245 -336 251 -322 269 -314 281 -320 247 -356 247 -320 283 -318 247 -320 283 -306 279 -320 269 -314 281 -320 287 -288 287 -290 287 -326 251 -326 289 -288 287 -290 323 -290 289 -288 289 -286 307 -276 285 -306 317 -288 287 -290 323 -292 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 289 -290 323 -290 287 -290 285 -278 307 -286 301 -316 285 -288 289 -326 291 -292 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 291 -290 291 -288 289 -324 289 -288 289 -288 287 -324 269 -278 319 -274 319 -288 287 -290 325 -292 291 -290 291 -288 291 -290 325 -290 289 -290 289 -290 291 -288 289 -324 289 -288 289 -288 287 -324 271 -276 319 -276 317 -286 289 -288 325 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 291 -288 325 -290 287 -290 287 -288 305 -278 319 -272 319 -286 287 -288 327 -288 291 -288 291 -290 291 -292 289 -328 291 -290 291 -290 289 -288 289 -326 287 -290 287 -290 287 -288 307 -278 283 -306 317 -288 287 -288 289 -328 291 -290 291 -292 289 -292 289 -292 327 -288 291 -290 289 -290 289 -290 323 -290 287 -290 287 -288 305 -276 285 -304 317 -286 289 -288 327 -290 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 289 -288 325 -288 289 -290 287 -288 307 -276 283 -306 317 -286 289 -288 327 -290 291 -288 291 -288 291 -290 325 -290 289 -290 291 -288 289 -288 325 -288 289 -288 289 -288 307 -276 283 -306 317 -286 289 -288 325 -292 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 291 -290 289 -288 289 -324 289 -288 289 -288 285 -308 277 -320 307 -282 289 -286
|
||||
RAW_Data: 289 -324 291 -288 291 -290 289 -292 289 -292 291 -326 291 -292 289 -292 289 -292 287 -326 289 -288 289 -288 289 -288 307 -276 285 -306 317 -286 289 -288 289 -328 289 -292 289 -292 291 -290 291 -290 327 -290 289 -290 289 -290 287 -290 325 -288 289 -288 287 -288 305 -598 573 -586 603 -580 587 -312 285 -288 289 -324 291 -548 313 -286 323 -290 561 -292 287 -306 317 -588 283 -288 575 -310 321 -272 277 -320 273 -318 287 -286 325 -288 291 -290 289 -580 311 -286 563 -612 575 -586 601 -308 271 -278 321 -578 577 -316 285 -602 577 -602 581 -282 321 -566 587 -580 305 -318 285 -288 583 -590 587 -292 285 -602 317 -290 287 -292 579 -590 291 -284 299 -318 285 -288 289 -324 563 -316 285 -290 287 -290 287 -326 289 -562 291 -320 273 -318 585 -284 285 -326 291 -290 291 -290 291 -288 325 -290 287 -564 581 -306 315 -556 611 -278 321 -564 289 -288 603 -318 287 -562 599 -308 271 -310 293 -312 283 -288 289 -324 291 -562 315 -288 287 -290 569 -290 311 -100928 131 -2416 97 -588 429 -694 735 -298 861 -132 97 -98 425 -132 391 -66 21529 -12790 67 -466 133 -1124 231 -1684 1549 -66 261 -66 13551 -13838 97 -1756 197 -764 293 -132 97 -198 195 -66 823 -164 1151 -66 10001 -10592 65 -3288 131 -700 99 -598 99 -1452 97 -922 65 -198 65 -426 361 -164 921 -396 111329 -12338 97 -360 63 -1348 129 -952 97 -166 197 -196 63 -264 227 -132 365 -134 131 -68 165 -132 131 -332 265 -98 731 -100 2307 -13200 67 -1196 131 -100 67 -432 165 -166 497 -228 165 -162 527 -98 2859 -12934 99 -1458 131 -672 65 -366 131 -200 463 -132 301 -398 131 -168 2053 -234 63485 -12070 67 -200 67 -298 67 -666 99 -398 65 -730 427 -68 397 -464 729 -166 133 -15990 231 -1674 165 -66 397 -398 65 -264 197 -134 1785 -7954 371 -288 249 -308 283 -312 283 -302 315 -282 277 -314 279 -320 271 -316 251 -308 283 -310 285 -338 283 -282 277 -314 279 -318 273 -316 251 -310 281 -310 285 -340 281 -282 277 -314 279 -320 271 -316 281 -278 313 -280 287 -340 281 -282 275 -314 279 -322 273 -316 251 -308 283 -310 285 -340 281 -282 277 -314 279 -318 271 -318 281 -278 281 -310 287 -340 281 -282 277 -314 279 -320 271 -316 281 -278 283 -310 285 -340 281 -284 277 -314 279 -318 271 -316 281 -278 313 -280 319 -308 283 -282 277 -314 279 -318 271 -318 249 -310 281 -312 285 -338 281 -282 277 -314 279 -320 271 -318 281 -278 281 -312 285 -340 281 -282 277 -314 279 -320 271 -318 249 -310
|
||||
RAW_Data: 281 -312 285 -338 281 -282 277 -314 279 -320 273 -316 279 -280 313 -280 287 -340 281 -282 277 -314 279 -318 271 -316 281 -278 283 -310 287 -338 281 -282 277 -314 279 -320 273 -316 251 -308 283 -310 285 -340 281 -282 277 -314 279 -320 271 -318 281 -278 281 -312 285 -340 281 -282 277 -314 279 -320 273 -316 279 -280 311 -282 285 -340 281 -282 277 -314 279 -318 271 -318 281 -278 281 -312 285 -340 281 -282 277 -314 279 -318 273 -316 281 -278 281 -312 285 -340 281 -282 279 -312 281 -318 271 -316 281 -278 283 -310 287 -340 281 -282 277 -314 279 -320 271 -316 281 -278 283 -310 287 -338 281 -284 277 -314 279 -318 271 -316 281 -280 281 -312 285 -340 281 -282 277 -314 279 -320 271 -318 249 -310 281 -312 285 -340 281 -282 277 -314 279 -318 271 -318 281 -278 313 -280 287 -340 281 -282 275 -316 279 -320 271 -316 279 -278 281 -312 287 -340 281 -282 277 -314 279 -318 271 -318 281 -278 281 -312 285 -340 281 -282 277 -314 279 -320 271 -318 249 -310 281 -310 287 -338 283 -282 277 -314 279 -318 271 -318 281 -278 311 -282 287 -338 281 -284 275 -314 281 -320 271 -316 251 -308 283 -310 285 -340 281 -282 277 -314 279 -320 271 -316 281 -278 281 -312 285 -340 281 -282 279 -314 279 -318 271 -316 281 -280 281 -312 285 -340 281 -282 277 -314 279 -318 271 -318 279 -280 311 -282 285 -340 283 -578 589 -578 603 -584 573 -308 287 -306 277 -318 273 -584 315 -286 325 -290 573 -310 269 -278 321 -576 317 -288 579 -312 285 -290 287 -288 305 -278 317 -272 319 -284 287 -290 325 -290 289 -288 291 -598 277 -324 563 -578 303 -316 551 -620 559 -614 297 -276 317 -282 275 -314 277 -288 303 -318 575 -590 579 -604 281 -282 599 -582 305 -316 285 -282 599 -290 283 -298 315 -580 279 -280 597 -604 281 -282 309 -280 603 -612 575 -592 575 -604 579 -310 277 -278 311 -296 313 -586 277 -282 597 -602 283 -322 289 -290 287 -292 289 -292 577 -310 319 -562 291 -300 579 -586 601 -308 271 -310 291 -578 317 -288 287 -290 579 -310 285 -324 269 -600 273 -318 285 -288 595 -282 321 -104332 67 -1318 99 -1840 233 -2242 65 -3254 97 -66 97 -398 197 -66 133 -894 131 -100 795 -100 111129 -13622 65 -234 297 -1160 97 -196 97 -230 195 -396 663 -300 197 -66 197 -298 165 -164 459 -132 1019 -14090 67 -1194 333 -300 167 -568 231 -166 697 -68 1883 -13118 65 -990 195 -858 131 -1020 99 -132 99 -132 695 -100 165 -134 963 -68 431 -98 1433 -13386 65 -658
|
||||
RAW_Data: 97 -262 199 -930 99 -430 1191 -66 163 -166 165 -268 199 -198 1185 -98 229 -12212 131 -958 99 -330 261 -266 63 -392 65 -960 99 -660 495 -98 1377 -98 885 -100 723 -164 103369 -16290 99 -666 165 -664 661 -166 19547 -16298 97 -266 233 -330 97 -266 165 -100 97 -134 263 -100 1653 -15398 99 -626 65 -2078 99 -834 197 -594 265 -298 397 -164 1099 -64 563 -66 14777 -12222 65 -666 99 -3046 97 -298 129 -1354 265 -132 133 -98 397 -100 429 -132 41913 -280 323 -278 275 -312 277 -320 309 -286 279 -276 311 -276 319 -276 321 -280 273 -312 277 -320 311 -286 279 -276 311 -278 319 -276 321 -280 273 -312 277 -320 311 -286 279 -276 309 -280 321 -276 321 -250 303 -314 277 -288 341 -284 279 -274 311 -278 319 -276 319 -280 275 -310 279 -320 311 -286 279 -274 311 -278 321 -274 321 -280 275 -312 277 -320 309 -288 279 -274 311 -278 319 -276 321 -280 273 -312 307 -292 309 -286 279 -276 311 -278 321 -276 321 -250 305 -312 277 -318 309 -286 279 -274 311 -278 319 -276 319 -280 275 -312 277 -322 309 -286 279 -276 311 -276 321 -276 319 -280 275 -312 277 -320 309 -286 279 -276 309 -278 321 -276 321 -280 305 -282 307 -290 309 -286 277 -278 309 -280 321 -276 321 -250 305 -312 277 -318 309 -286 279 -274 311 -278 319 -276 321 -278 277 -312 275 -322 309 -286 279 -274 311 -278 319 -276 321 -280 275 -312 277 -320 309 -288 279 -274 311 -278 321 -274 321 -282 305 -280 309 -292 309 -284 279 -276 309 -280 321 -274 321 -250 305 -312 277 -318 309 -286 279 -274 311 -278 319 -276 321 -280 275 -312 277 -320 309 -286 279 -276 311 -278 319 -276 319 -280 275 -312 277 -320 311 -286 279 -276 311 -278 319 -276 321 -250 337 -282 307 -290 311 -284 279 -276 311 -278 317 -276 321 -278 275 -312 277 -320 309 -286 279 -274 311 -278 321 -274 321 -280 275 -312 277 -320 309 -286 279 -276 311 -278 319 -276 319 -280 275 -312 277 -320 309 -286 279 -278 309 -278 321 -276 323 -250 335 -282 277 -320 309 -286 277 -276 311 -278 319 -274 321 -280 275 -312 277 -320 309 -286 279 -274 311 -278 321 -274 321 -280 275 -312 277 -320 309 -286 279 -274 311 -278 321 -276 321 -278 277 -310 309 -290 311 -284 279 -276 311 -280 321 -276 321 -250 303 -314 277 -286 341 -286 277 -276 311 -278 319 -274 321 -280 275 -312 277 -320 309 -288 277 -276 311 -278 319 -276 319 -280 275 -312 277 -320 309 -288 279 -274 311 -278 319 -276 321 -282 273 -312 307 -290 311 -286
|
||||
RAW_Data: 277 -278 309 -280 321 -276 321 -250 303 -312 277 -320 309 -552 611 -562 609 -566 615 -284 323 -290 291 -292 289 -572 291 -280 323 -276 587 -318 289 -292 289 -580 307 -288 593 -294 299 -278 323 -288 289 -288 327 -254 325 -292 289 -292 291 -290 291 -290 291 -602 277 -324 565 -288 285 -304 319 -288 289 -588 575 -310 289 -592 291 -296 579 -590 601 -274 307 -276 319 -576 319 -292 581 -278 323 -290 289 -566 613 -276 319 -558 317 -290 579 -592 577 -304 315 -554 317 -288 327 -256 577 -308 325 -252 309 -276 317 -578 317 -292 581 -590 309 -272 597 -578 613 -564 309 -272 599 -578 613 -562 611 -562 317 -288 585 -284 323 -292 289 -572 291 -280 619 -588 317 -290 573 -274 307 -274 319 -578 319 -288 291 -290 579 -312 287 -111108 165 -464 63 -532 433 -132 199 -66 865 -132 163 -98 195 -100 457 -130 14007 -12138 165 -134 97 -98 65 -396 65 -1516 97 -1316 229 -296 165 -266 133 -234 199 -66 133 -100 331 -530 1677 -66 195 -64 129 -12166 99 -2058 167 -332 65 -1128 363 -232 1023 -100 129 -166 665 -15130 133 -1332 65 -3074 329 -200 133 -166 229 -164 331 -98 65 -194 953 -66 1087 -16564 165 -798 65 -1710 65 -98 131 -332 97 -134 97 -166 663 -432 1615 -12104 63 -298 67 -1026 231 -2608 67 -332 65 -166 65 -98 63 -164 197 -98 165 -522 587 -528 25573 -14058 67 -2468 65 -360 199 -730 497 -66 599 -66 233 -134 267 -364 11655 -12368 99 -66 165 -132 65 -698 67 -1130 99 -1326 167 -434 65 -66 197 -230 129 -100 229 -264 53271 -12130 229 -198 65 -2600 65 -166 165 -266 165 -462 99 -132 133 -66 297 -232 231 -298 229 -66 165 -132 67 -100 197 -68 233 -66 6037 -10966 65 -1164 361 -1842 165 -1892 467 -298 333 -66 131 -232 265 -266 54643 -15106 65 -798 99 -368 165 -166 431 -198 631 -330 131 -264 1613 -66 31431 -5876 417 -254 289 -326 287 -290 289 -288 287 -308 277 -284 303 -316 287 -288 289 -290 325 -290 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -292 289 -292 289 -326 287 -290 289 -288 289 -286 307 -276 317 -274 319 -286 289 -288 291 -328 289 -292 289 -292 291 -290 291 -288 327 -292 289 -290 289 -290 289 -290 325 -288 289 -288 287 -288 287 -308 275 -320 273 -318 287 -288 289 -326 291 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -288 289 -324 289 -288 289 -288 287 -324 271 -278 319 -274 319 -286 289 -288 325 -290 289 -290 291 -290 291 -288 291 -326 289 -292 289 -292 291 -290
|
||||
RAW_Data: 289 -324 289 -288 289 -288 287 -324 271 -276 319 -274 317 -286 289 -288 291 -328 289 -292 289 -292 291 -290 291 -290 291 -328 289 -292 291 -288 291 -288 289 -324 289 -288 289 -288 321 -272 277 -318 271 -318 285 -288 289 -328 289 -292 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 291 -290 289 -324 289 -290 287 -290 287 -324 271 -276 319 -274 317 -288 287 -290 325 -290 289 -290 291 -288 291 -288 327 -290 289 -290 289 -290 287 -290 325 -288 289 -288 289 -288 307 -276 283 -306 317 -286 287 -290 325 -292 289 -292 291 -290 291 -290 291 -292 289 -328 291 -290 291 -290 291 -288 289 -326 287 -290 287 -290 287 -308 275 -286 303 -318 285 -286 327 -288 291 -290 289 -292 289 -292 291 -290 291 -328 289 -292 289 -292 291 -290 289 -288 325 -288 289 -288 289 -288 277 -306 283 -306 317 -286 287 -288 325 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -290 289 -290 289 -290 325 -288 289 -288 287 -286 305 -278 317 -274 317 -286 287 -290 289 -328 291 -290 291 -290 291 -292 289 -292 289 -328 291 -290 291 -292 289 -292 287 -290 323 -290 289 -288 289 -288 321 -272 277 -320 273 -320 287 -288 325 -288 291 -290 289 -290 289 -290 327 -288 291 -288 291 -292 289 -290 287 -326 287 -290 287 -290 287 -324 271 -276 319 -274 317 -286 289 -288 291 -328 289 -292 291 -290 291 -290 291 -290 291 -328 289 -292 291 -572 579 -592 611 -572 605 -290 293 -276 319 -286 287 -596 281 -324 289 -290 571 -310 271 -312 291 -578 317 -288 581 -312 285 -288 289 -288 307 -276 285 -306 317 -288 287 -288 291 -598 601 -582 585 -578 621 -562 613 -264 305 -584 317 -288 579 -588 307 -272 277 -322 575 -318 287 -598 277 -322 563 -290 283 -600 579 -588 309 -286 307 -276 317 -274 583 -622 277 -286 325 -290 287 -290 595 -588 273 -318 287 -286 289 -328 549 -316 285 -290 287 -326 287 -564 291 -320 271 -318 585 -284 285 -324 289 -290 289 -288 289 -326 287 -290 287 -290 287 -594 293 -296 577 -318 287 -290 289 -290 325 -290 291 -288 291 -288 289 -602 577 -302 277 -318 281 -276 311 -280 317 -576 317 -276 277 -312 575 -314 285 -106540 197 -300 397 -2220 99 -100 97 -68 331 -132 301 -366 25943 -12936 133 -466 165 -2160 99 -134 165 -166 65 -134 261 -338 65 -132 131 -430 267 -364 891 -232 52935 -16770 229 -164 63 -196 129 -198 327 -64 45247 -12242 63 -528 65 -334 67 -2548 195 -66 231 -564 265 -232 231 -296 1593 -15476 199 -232
|
||||
RAW_Data: 65 -68 133 -198 65 -332 165 -202 165 -100 65 -164 131 -300 429 -66 99 -100 197 -66 197 -200 51785 -11938 133 -594 295 -132 161 -100 65 -98 657 -66 229 -164 1051 -66 619 -66 89339 -9946 65 -2232 131 -528 97 -328 99 -164 295 -330 163 -2108 229 -258 529 -298 263 -132 199 -134 167 -102 267 -68 99 -66 199 -132 465 -66 2321 -16774 65 -364 495 -166 199 -298 963 -98 265 -66 133 -98 99 -100 465 -98 55603 -12124 65 -268 99 -164 231 -430 67 -298 65 -434 163 -166 65 -730 133 -1032 201 -268 397 -200 65 -134 197 -100 265 -66 201 -66 569 -12626 65 -2892 67 -732 365 -298 131 -66 133 -132 99 -464 265 -68 329 -100 131 -100 795 -64 13215 -12674 131 -392 99 -390 131 -132 97 -424 131 -462 131 -786 131 -566 199 -134 131 -100 435 -66 201 -66 167 -266 1455 -100 65 -132 165 -134 6529 -284 287 -290 327 -252 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 289 -290 325 -252 309 -276 317 -276 319 -290 289 -292 289 -292 327 -254 327 -290 291 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 289 -290 325 -288 273 -276 317 -276 319 -290 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 287 -290 323 -242 307 -284 305 -320 287 -290 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 289 -290 307 -276 315 -274 319 -288 289 -290 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 289 -290 287 -290 307 -276 317 -276 319 -290 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 327 -254 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 291 -290 289 -290 325 -272 273 -318 309 -286 289 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -290 291 -292 325 -256 327 -290 289 -290 287 -290 305 -278 315 -274 319 -288 289 -290 291 -290 327 -254 327 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -290 291 -288 325 -272 275 -286 341 -286 287 -288 327 -252 327 -288 291 -292 289 -292 289 -292
|
||||
RAW_Data: 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -288 325 -272 275 -318 309 -286 289 -290 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 327 -256 325 -292 291 -290 291 -290 291 -292 289 -292 327 -254 327 -290 291 -292 289 -292 289 -290 307 -274 315 -274 319 -290 287 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -254 327 -292 289 -292 291 -570 609 -566 613 -574 589 -290 319 -270 319 -288 289 -564 319 -288 291 -290 611 -276 323 -272 275 -614 319 -252 585 -316 287 -292 289 -292 327 -290 291 -290 291 -292 289 -292 287 -290 307 -564 309 -320 585 -574 313 -288 289 -292 289 -290 323 -272 275 -318 573 -320 287 -566 313 -290 569 -610 299 -278 585 -320 287 -566 313 -288 327 -254 601 -288 281 -602 579 -590 309 -290 323 -254 595 -594 311 -288 585 -572 619 -258 321 -276 319 -558 319 -274 597 -292 299 -278 321 -574 313 -280 281 -300 317 -250 335 -282 599 -576 581 -604 293 -276 315 -274 585 -320 273 -600 585 -314 277 -278 309 -278 321 -276 321 -572 573 -614 577 -284 323 -292 289 -566 309 -276 307 -282 603 -286 307 -99998 133 -13620 65 -498 99 -294 65 -564 167 -200 111009 -434 67 -12966 65 -200 67 -834 99 -300 65 -560 133 -764 427 -100 331 -66 165 -166 365 -166 695 -98 1393 -168 33001 -12322 65 -132 133 -100 501 -562 99 -98 131 -1556 195 -494 497 -464 97 -134 131 -332 429 -200 1125 -166 497 -12936 67 -2256 463 -1354 197 -266 67 -962 99 -898 231 -302 329 -100 163 -330 167 -132 265 -132 565 -13934 63 -1150 65 -198 101 -866 97 -234 65 -1462 99 -1162 467 -66 167 -502 99 -66 231 -166 4361 -16590 97 -328 197 -596 65 -230 65 -132 197 -98 593 -164 263 -12202 131 -330 99 -3412 65 -98 131 -232 135 -366 129 -296 561 -166 131 -132 967 -68 65 -100 163 -132 23143 -12090 161 -458 65 -926 131 -194 131 -524 99 -498 133 -730 627 -100 397 -100 165 -66 997 -66 499 -66 16935 -14014 63 -2484 65 -164 63 -330 65 -164 65 -396 657 -266 527 -16644 131 -2454 65 -166 165 -100 165 -198 133 -266 23693 -12798 65 -526 165 -1118 65 -590 99 -1028 133 -100 99 -200 459 -66 99 -198 52179 -12650 131 -68 65 -266 197 -68 65 -100 363 -66 133 -132 65 -132 2559 -98 35877 -278 327 -242 307 -284 341 -282 289 -290 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -290 327 -256 327 -290 291 -290 291 -290
|
||||
RAW_Data: 291 -288 325 -272 275 -288 341 -286 287 -288 327 -254 325 -256 325 -292 291 -290 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 291 -292 289 -292 287 -290 307 -276 317 -274 321 -288 291 -290 291 -292 325 -256 327 -290 291 -290 291 -292 325 -256 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 289 -290 323 -274 273 -318 307 -286 289 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -290 291 -290 289 -288 287 -280 305 -284 303 -320 287 -288 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 289 -292 327 -290 291 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 287 -290 307 -276 315 -274 321 -288 289 -290 327 -252 327 -290 291 -292 289 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 287 -290 307 -276 317 -274 321 -288 291 -290 291 -292 325 -292 291 -290 291 -290 291 -292 325 -256 327 -254 327 -290 291 -292 289 -292 289 -292 327 -254 327 -292 289 -292 289 -292 289 -290 323 -272 275 -318 309 -286 287 -290 289 -292 327 -254 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -292 287 -290 287 -278 305 -284 303 -318 287 -290 289 -292 327 -254 327 -292 289 -292 289 -292 291 -290 291 -290 327 -292 289 -292 291 -290 291 -290 291 -292 325 -292 291 -290 291 -290 291 -288 289 -288 309 -276 317 -276 321 -288 289 -290 327 -252 327 -288 291 -290 291 -292 289 -292 289 -292 327 -290 291 -292 289 -292 289 -292 291 -290 327 -292 289 -292 289 -292 289 -290 287 -310 273 -284 341 -286 287 -290 291 -290 327 -254 327 -292 289 -292 289 -292 291 -290 327 -254 327 -292 289 -574 577 -594 611 -556 595 -294 307 -292 313 -286 289 -562 319 -288 325 -256 609 -276 307 -276 317 -576 317 -290 581 -280 323 -292 291 -290 291 -290 291 -288 325 -242 305 -286 341 -284 289 -288 325 -562 573 -312 287 -288 325 -558 291 -300 583 -320 287 -292 291 -564 597 -578 595 -582 611 -312 287 -564 289 -320 275 -322 287 -290 585 -592 591 -290 281 -300 317 -286 289 -586 607 -274 323 -560 291 -298 313 -288 585 -286 289 -602 313 -252 601 -290 281 -304 317 -290 287 -596 285 -288 327 -254 327 -290 291 -292 289 -292 569 -292 309 -594 579 -316 289 -290 325 -534
|
||||
RAW_Data: 291 -310 295 -316 585 -592 277 -290 601 -578 601 -588 573 -312 287 -288 325 -558 291 -302 317 -286 587 -286 289 -109798 163 -334 165 -2588 297 -98 361 -100 165 -100 299 -66 67 -300 1855 -98 60919 -13806 297 -64 67 -398 133 -200 135 -366 195 -198 131 -166 2673 -12960 65 -294 165 -460 97 -1744 65 -494 193 -66 393 -528 65 -98 99 -690 65 -198 589 -164 65 -66 823 -132 97 -164 1045 -12034 131 -664 163 -98 65 -728 97 -2390 67 -230 959 -100 231 -164 129 -164 99 -168 597 -166 1259 -12120 63 -1258 201 -798 65 -1224 131 -332 231 -100 97 -68 231 -198 165 -260 195 -198 265 -132 63 -100 297 -162 755 -15332 131 -332 133 -1024 293 -426 65 -164 131 -266 65 -498 99 -330 63 -98 229 -230 195 -328 395 -232 2241 -66 33257 -13812 131 -692 165 -562 97 -234 431 -166 501 -362 165 -134 231 -66 665 -68 165 -66 4573 -12796 99 -130 65 -364 197 -360 229 -264 99 -66 131 -262 63 -296 197 -132 99 -98 2203 -64 5999 -10490 167 -1488 99 -888 97 -1182 525 -66 97 -132 163 -196 197 -262 1803 -66 231 -98 21905 -10752 165 -264 67 -1856 131 -100 65 -792 131 -364 97 -66 231 -100 597 -100 263 -12290 97 -986 65 -230 65 -558 65 -294 97 -230 163 -260 493 -132 827 -66 14053 -13310 65 -332 99 -166 165 -1522 231 -100 231 -134 167 -200 131 -168 233 -68 799 -98 131 -66 26437 -10218 99 -1024 199 -530 67 -764 65 -202 65 -504 65 -960 65 -424 163 -66 297 -134 99 -234 2891 -11318 131 -1716 197 -364 131 -166 67 -2028 99 -232 133 -498 893 -200 65 -134 697 -232 21743 -12402 97 -690 65 -532 65 -1062 99 -1494 163 -66 131 -234 165 -334 233 -396 231 -230 40835 -6598 99 -1362 129 -3056 65 -298 165 -366 131 -398 265 -132 565 -200 595 -166 361 -66 3499 -10248 67 -196 131 -326 97 -262 97 -362 65 -296 129 -626 131 -200 63 -788 131 -164 95 -228 229 -100 425 -296 2071 -16092 197 -132 161 -232 163 -462 163 -100 491 -66 65 -132 717 -68 629 -66 50157 -12134 65 -200 131 -198 129 -362 65 -624 67 -1560 133 -198 265 -134 233 -132 201 -134 167 -166 131 -200 231 -68 365 -100 401 -66 76485 -12816 65 -3776 165 -300 99 -234 561 -334 231 -266 793 -66 335 -66 93749 -12204 199 -468 65 -66 165 -560 65 -1232 131 -196 757 -132 329 -132 129 -394 1255 -66 15647 -12870 131 -3596 493 -132 263 -234 231 -330 165 -12280 97 -668 199 -134 99 -298 199 -1322 65 -426 295 -294 329 -462 131 -526 65 -230 495 -98 925 -100 295 -136 3359 -12764 97 -102 99 -1930
|
||||
RAW_Data: 99 -132 299 -400 197 -100 131 -66 65 -66 959 -68 2277 -16686 67 -364 131 -402 197 -200 433 -132 333 -168 73513 -12168 65 -3606 63 -688 63 -262 65 -462 97 -494 167 -166 163 -166 1489 -68 65 -166 67219 -4166 97 -10962 99 -196 65 -854 165 -100 131 -100 97 -100 65 -98 197 -396 295 -396 131 -12412 165 -526 65 -890 65 -558 131 -492 131 -230 129 -198 197 -132 97 -98 97 -398 165 -132 559 -64 1277 -360 1065 -12304 133 -100 133 -466 197 -1588 863 -100 299 -132 65 -200 162275 -12932 199 -464 233 -664 331 -134 363 -132 331 -100 199 -98 2183 -12224 131 -1200 65 -1660 363 -264 465 -100 165 -66 299 -362 1675 -132 65 -66 4343 -13428 197 -2114 165 -496 423 -330 491 -100 99 -264 133 -132 133 -100 467 -100 1609 -13646 99 -298 101 -2190 131 -100 133 -332 97 -398 99 -100 65 -296 925 -232 197 -230 265 -168 131 -364 23685 -12448 65 -700 197 -132 165 -166 67 -996 99 -370 131 -458 197 -64 129 -98 263 -198 263 -98 1653 -98 63409 -12350 199 -566 67 -198 133 -432 167 -402 429 -300 197 -132 297 -132 197 -134 29593 -12186 265 -794 67 -330 133 -66 297 -200 65 -134 97 -134 231 -100 1545 -12844 131 -298 99 -100 65 -366 195 -1722 99 -722 295 -100 227 -64 195 -66 197 -130 429
|
||||
@@ -17,7 +17,7 @@ A terminal-based RF signal analysis tool for capturing, decoding, and retransmit
|
||||
## Features
|
||||
|
||||
- **Real-time capture** — receive and demodulate AM/OOK keyfob signals at configurable frequencies (HackRF uses AM envelope detection; FM protocols are tagged for display and may decode when signal is strong)
|
||||
- **Multi-protocol decoding** — 14 protocol decoders: Kia V0–V6, Ford V0, Fiat V0, Subaru, Suzuki, VAG (VW/Audi/Seat/Skoda), PSA, Scher-Khan, Star Line; adaptive demodulation for real-world conditions
|
||||
- **Multi-protocol decoding** — 32 protocol decoders: Kia V0–V7, Ford V0–V3, Chrysler V0 (Dodge/Jeep), Honda Static, Honda V1, Toyota (Lexus), Land Rover V0, Land Rover RKE, Fiat V0/V1, Mazda V0, Mazda Siemens, Mitsubishi V0, BMW CAS4, Porsche Touareg, Porsche Cayenne, Subaru, Suzuki, VAG (VW/Audi/Seat/Skoda), PSA, PSA2, Scher-Khan, Star Line; adaptive demodulation for real-world conditions
|
||||
- **KeeLoq generic fallback** — when a signal doesn’t match any known protocol, KAT tries decoding it as KeeLoq using every manufacturer key in the embedded keystore (Kia V3/V4 and Star Line air formats); successful decodes appear as **Keeloq (keystore name)** in the capture list
|
||||
- **RF modulation metadata** — each protocol tagged as AM, FM, or both (from ProtoPirate); shown in signal detail and exported in .fob
|
||||
- **Rich signal detail** — encoding (PWM/Manchester), RF (AM/FM), encryption, serial, counter, key data, CRC, frequency, and raw level/duration pairs
|
||||
@@ -27,7 +27,7 @@ A terminal-based RF signal analysis tool for capturing, decoding, and retransmit
|
||||
- **Research mode** — config option to show unknown (unidentified) signals in addition to successfully decoded ones
|
||||
- **INI configuration** — `~/.config/KAT/config.ini` (auto-created with comments on first run): export path, max captures, research_mode, radio defaults, export format
|
||||
- **Embedded keystore** — manufacturer keys (Kia, VAG, etc.) built in for decoding
|
||||
- **VIM-style command line** — `:freq`, `:lock`, `:unlock`, `:save`, `:load`, `:delete`, `:q` / `:quit`, and more
|
||||
- **VIM-style command line** — `:freq`, `:lock`, `:unlock`, `:replay`, `:save`, `:load`, `:delete`, `:q` / `:quit`, and more
|
||||
- **Interactive TUI** — captures list with detail panel (protocol, freq, mod, RF, encryption), signal action menu, radio settings, fob export form; header shows device (HackRF / RTL-SDR (RX only) / No device) and status (DISCONNECTED in red when no device)
|
||||
|
||||
## Requirements
|
||||
@@ -119,11 +119,12 @@ Press `Enter` on a capture to open the action menu. When using RTL-SDR (receive-
|
||||
|
||||
When exporting to `.fob`, a metadata form collects filename and optional vehicle info:
|
||||
|
||||
- **File** — output filename (extension added by format)
|
||||
- **File** — output filename (extension added by format). For unknown protocol, a unique 8-character hex suffix (e.g. `A1B2C3D4`) is shown in the field and appended to the filename so each export has a distinct file.
|
||||
- **Year** — vehicle model year
|
||||
- **Make** — manufacturer (auto-suggested from protocol)
|
||||
- **Model** — vehicle model
|
||||
- **Region** — region/market
|
||||
- **Command** — button/command label (e.g. Unlock, Lock, Trunk, Panic); used in the default filename for unknown protocol and stored in the .fob vehicle section.
|
||||
- **Notes** — free-form notes
|
||||
|
||||
The exported `.fob` file is a versioned JSON document (v2.0, format `kat-fob`) containing:
|
||||
@@ -153,17 +154,22 @@ The exported `.fob` file is a versioned JSON document (v2.0, format `kat-fob`) c
|
||||
"year": 2023,
|
||||
"make": "Kia",
|
||||
"model": "Sportage",
|
||||
"region": "",
|
||||
"region": "NA",
|
||||
"command": "Lock",
|
||||
"notes": ""
|
||||
},
|
||||
"capture": {
|
||||
"timestamp": "2026-02-07T12:00:00Z",
|
||||
"raw_data_hex": "0x...",
|
||||
"raw_pair_count": 0,
|
||||
"raw_pairs": [{"level": true, "duration_us": 400}, {"level": false, "duration_us": 800}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`rf_modulation` is AM, FM, or AM/FM per protocol (from ProtoPirate). Raw pairs are included when config `include_raw_pairs` is true.
|
||||
- **vehicle.command** — optional; user-editable command label (e.g. Unlock, Lock). Set in the export form or via **i** (capture metadata). Used for unknown-protocol export filenames (`Year_Make_Model_Region_Command_8HEX.fob`).
|
||||
- **rf_modulation** — AM, FM, or AM/FM per protocol (from ProtoPirate). Omitted when unknown.
|
||||
- **capture** — `raw_data_hex`, `raw_pair_count`, and optionally `raw_pairs` when config `include_raw_pairs` is true.
|
||||
|
||||
### VIM-Style Commands
|
||||
|
||||
@@ -172,10 +178,11 @@ Transmit commands (`:lock`, `:unlock`, `:trunk`, `:panic`) require HackRF; with
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `:freq <MHz>` | Set receive frequency (e.g. `:freq 433.92`) |
|
||||
| `:lock <ID>` | Transmit lock signal for capture ID |
|
||||
| `:unlock <ID>` | Transmit unlock signal for capture ID |
|
||||
| `:trunk <ID>` | Transmit trunk release signal |
|
||||
| `:panic <ID>` | Transmit panic alarm signal |
|
||||
| `:lock <ID>` | Transmit lock signal (ID: single, comma list, or range; e.g. `1`, `1, 3, 5`, `1-5`) |
|
||||
| `:unlock <ID>` | Transmit unlock signal (same ID formats) |
|
||||
| `:trunk <ID>` | Transmit trunk release (same ID formats) |
|
||||
| `:panic <ID>` | Transmit panic alarm (same ID formats) |
|
||||
| `:replay <ID>` | Replay raw capture(s) by ID in order (same ID formats; HackRF only) |
|
||||
| `:save <ID>` | Save capture to file |
|
||||
| `:delete <ID>` | Delete capture from list |
|
||||
| `:load <file>` | Import capture from `.fob` or `.sub` file |
|
||||
@@ -212,7 +219,7 @@ include_raw_pairs = true
|
||||
```
|
||||
|
||||
- **import_directory** — directory scanned at startup for .fob and .sub files to import (default `~/.config/KAT/import`). Exports are still saved to **export_directory**.
|
||||
- **research_mode** — when `false` (default), only successfully decoded signals appear in the list; when `true`, unknown (unidentified) signals are also shown.
|
||||
- **research_mode** — when `true` (default), unknown (unidentified) signals are shown in addition to decoded ones; when `false`, only successfully decoded signals appear.
|
||||
- **include_raw_pairs** — when `true`, .fob exports include raw level/duration pairs for replay.
|
||||
|
||||
## Supported Protocols
|
||||
@@ -226,21 +233,55 @@ Protocol behavior and RF modulation (AM/FM) follow the ProtoPirate reference. KA
|
||||
| Kia V2 | Manchester | FM | Fixed Code | 315 / 433.92 MHz |
|
||||
| Kia V3/V4 | PWM | AM/FM | KeeLoq | 315 / 433.92 MHz |
|
||||
| Kia V5 | Manchester | FM | Fixed Code | 433.92 MHz |
|
||||
| Kia V6 | Manchester | FM | Fixed Code | 433.92 MHz |
|
||||
| Ford V0 | Manchester | FM | Fixed Code | 433.92 MHz |
|
||||
| Kia V6 | Manchester | FM | AES-128 | 433.92 MHz |
|
||||
| Kia V7 | Manchester | FM | Fixed Code (CRC8) | 315 / 433.92 MHz |
|
||||
| Ford V0 | Manchester | FM | Rolling Code | 315 / 433.92 MHz |
|
||||
| Ford V1 | Manchester | FM | Rolling Code (descramble + CRC16) | 315 / 433.92 MHz |
|
||||
| Ford V2 | Manchester | FM | Fixed Code (0x7FA7 sync) | 315 / 433.92 MHz |
|
||||
| Ford V3 | Manchester | FM | Fixed Code (decode-only) | 315 / 433.92 MHz |
|
||||
| Chrysler V0 (Dodge/Jeep) | PWM | AM | Rolling Code (seed-XOR) | 315 / 433.92 MHz |
|
||||
| Honda Static | Manchester | FM | Fixed Code | 315 / 433.92 MHz |
|
||||
| Honda V1 | PWM | AM | Fixed Code | 315 / 433.92 MHz |
|
||||
| Toyota (Lexus) | PWM / NRZ | AM | KeeLoq (decode-only) | 315 / 433.92 MHz |
|
||||
| Land Rover V0 | Diff. Manchester | FM | Rolling Code (3-bit check) | 315 / 433.92 MHz |
|
||||
| Land Rover RKE | PWM | AM | KeeLoq | 315 / 433.92 MHz |
|
||||
| Fiat V0 | Manchester | FM | Fixed Code | 433.92 MHz |
|
||||
| Fiat V1 (Magneti Marelli) | Manchester | FM | Rolling Code | 433.92 MHz |
|
||||
| Mazda V0 | Pair-based | FM | XOR Deobfuscation | 433.92 MHz |
|
||||
| Mazda Siemens | Manchester | FM | Siemens XOR/interleave | 433.92 MHz |
|
||||
| Mitsubishi V0 | PWM | FM | Bit Negation + XOR | 868.35 MHz |
|
||||
| BMW CAS4 | Manchester | AM | CAS4 rolling (decode-only) | 433.92 MHz |
|
||||
| Porsche Touareg | PWM | AM | Rotation Cipher | 433.92 / 868.35 MHz |
|
||||
| Porsche Cayenne | PWM | AM | VAG rolling register | 433.92 / 868.35 MHz |
|
||||
| Subaru | PWM | AM | Rolling Code | 433.92 MHz |
|
||||
| Suzuki | PWM | AM | Rolling Code | 433.92 MHz |
|
||||
| VAG (VW/Audi/Seat/Skoda) | Manchester | AM | AUT64/XTEA | 433.92 / 434.42 MHz (decode + encode) |
|
||||
| VAG (VW/Audi/Seat/Skoda) | Manchester | AM | AUT64/XTEA | 433.92 / 434.42 MHz |
|
||||
| Scher-Khan | PWM | FM | Magic Code | 433.92 MHz |
|
||||
| Star Line | PWM | AM | KeeLoq | 433.92 MHz |
|
||||
| PSA (Peugeot/Citroën) | Manchester | FM | XTEA/XOR | 433.92 MHz |
|
||||
| PSA (Peugeot/Citroën) | Manchester | FM | Modified TEA/XOR | 433.92 MHz |
|
||||
| PSA2 (PSA OLD) | Manchester | AM | TEA | 433.92 MHz |
|
||||
|
||||
**KeeLoq generic fallback:** If no protocol decodes a capture, KAT tries KeeLoq with every keystore manufacturer key (Kia V3/V4 and Star Line bit layouts). On success the protocol is shown as **Keeloq (*keystore name*)** (e.g. Keeloq (Alligator), Keeloq (Pandora_PRO)). See [docs/keeloq_generic.md](docs/keeloq_generic.md).
|
||||
|
||||
### Vulnerability database
|
||||
|
||||
KAT matches capture metadata (Year / Make / Model / Region, set via **i** on a capture) against a built-in CVE list. The **Vuln Found** column and **Vulnerability** detail panel show matches; each CVE links to the NVD for further reading.
|
||||
|
||||
| CVE | Make(s) | Models | Year range | Description |
|
||||
|-----|---------|--------|------------|-------------|
|
||||
| [CVE-2022-38766](https://nvd.nist.gov/vuln/detail/CVE-2022-38766) | Renault | ZOE | 2020–2022 | Replay attack: same rolling code set per door-open request (433.92 MHz). |
|
||||
| [CVE-2022-27254](https://nvd.nist.gov/vuln/detail/CVE-2022-27254) | Honda | Civic | 2016–2019 | Replay attack: same RF signal per door-open request (related to CVE-2019-20626). |
|
||||
| [CVE-2019-20626](https://nvd.nist.gov/vuln/detail/CVE-2019-20626) | Honda, Acura | HR-V, Civic, Accord; TSX | 2009, 2016–2020 (by model) | Replay attack: same RF signal per door-open request (static code, no rolling code). Confirmed vehicles per [Unoriginal-Rice-Patty](https://github.com/HackingIntoYourHeart/Unoriginal-Rice-Patty). |
|
||||
| [CVE-2022-37418](https://nvd.nist.gov/vuln/detail/CVE-2022-37418) | Honda, Hyundai, Kia, Nissan | Fit (hybrid), Fit, City, Vezel; Elantra; Cerato, Forte, K3; Latio, Sylphy | 2007–2022 (varies by model) | **RollBack attack:** RKE allows unlock and resync after capturing two consecutive key fob signals; attacker can unlock indefinitely. |
|
||||
| [CVE-2022-36945](https://nvd.nist.gov/vuln/detail/CVE-2022-36945) | Mazda | 3, 2 Sedan, 2 HB (facelift), Cx-3, Cx-5 | 2018–2020 (varies by model) | **RollBack attack:** RKE allows unlock and resync after capturing **three** consecutive key fob signals; attacker can unlock indefinitely. |
|
||||
|
||||
Details (exact model/year scope) are in `src/vuln_db.rs`. The app shows the NVD URL for each matched CVE in the Vulnerability panel.
|
||||
|
||||
### Cryptographic modules
|
||||
|
||||
- **KeeLoq** — encrypt/decrypt with normal, secure, FAAC, and magic serial/XOR learning key derivation (keeloq_common, keys). Unknown signals are tried as KeeLoq with every keystore key via **keeloq_generic** (uses keeloq_common only).
|
||||
- **AES-128** — full encrypt/decrypt for Kia V6 (key derived from keystore A/B with XOR masks)
|
||||
- **Modified TEA** — XTEA-like cipher with dynamic key selection for PSA (Peugeot/Citroën)
|
||||
- **AUT64** — 12-round block cipher for VAG type 1/3/4 (aut64)
|
||||
- **Keystore** — manufacturer keys (Kia, VAG, etc.) built in; see `src/keystore/`
|
||||
|
||||
@@ -253,7 +294,7 @@ The **AM/OOK** demodulator turns IQ samples into level/duration pairs for protoc
|
||||
- **Exponential moving average** — magnitude smoothing
|
||||
- **Schmitt trigger hysteresis** — reduces chattering at the decision boundary
|
||||
- **Debounce** — 40µs minimum pulse width to reject noise spikes
|
||||
- **Gap detection** — 20 ms gap treated as end of signal
|
||||
- **Gap detection** — 80 ms gap treated as end of signal (multi-burst keyfob presses stay one capture)
|
||||
|
||||
## IMPORTS folder
|
||||
|
||||
@@ -281,7 +322,8 @@ src/
|
||||
│ ├── aut64.rs # AUT64 block cipher (VAG)
|
||||
│ ├── keys.rs # Key loading (embedded + optional file), KIA/VAG
|
||||
│ ├── kia_v0..kia_v6.rs
|
||||
│ ├── ford_v0.rs, fiat_v0.rs, subaru.rs, suzuki.rs
|
||||
│ ├── ford_v0.rs, fiat_v0.rs, fiat_v1.rs, subaru.rs, suzuki.rs
|
||||
│ ├── mazda_v0.rs, mitsubishi_v0.rs, porsche_touareg.rs
|
||||
│ ├── vag.rs # VAG decoder/encoder (4 sub-types; encode from capture via extra)
|
||||
│ ├── scher_khan.rs, star_line.rs, psa.rs
|
||||
│ └── ...
|
||||
@@ -299,21 +341,20 @@ src/
|
||||
└── status_bar.rs # Status bar
|
||||
```
|
||||
|
||||
## Call for researchers
|
||||
|
||||
**We need your help.** KAT’s protocol decoders and future analysis depend on real-world keyfob captures. If you use KAT for security research, authorized testing, or protocol development, please consider:
|
||||
|
||||
- **Contributing to protocol analysis** — share timing, encoding, or decoder feedback; report bugs or suggest new protocols.
|
||||
- **Contributing captures to the research library** — add your `.fob` files to the **[FOB Research Library](https://github.com/KaraZajac/FOB_Research_Library)** so others can use them for decoder development, CVE validation, and protocol studies.
|
||||
|
||||
The [FOB Research Library](https://github.com/KaraZajac/FOB_Research_Library) is a community collection of KAT `.fob` captures organized by manufacturer. Your submissions (with proper metadata: year, make, model, region, command) help grow test vectors and support the wider keyfob research community.
|
||||
|
||||
---
|
||||
|
||||
## Credits
|
||||
|
||||
KAT is developed by **Kara Zajac (.leviathan)**. KAT would not be possible without [ProtoPirate](https://protopirate.net/ProtoPirate/ProtoPirate)—the protocol decoders, reference implementations, and community work are the foundation this tool is built on. Truly standing on the shoulders of giants.
|
||||
|
||||
### ProtoPirate Development Team
|
||||
|
||||
RocketGod, MMX, Leeroy, gullradriel, Skorp (thanks for the Weather App inspiration!), Vadim's Radio Driver.
|
||||
|
||||
### Protocol Magic
|
||||
|
||||
L0rdDiakon, YougZ, RocketGod, MMX, DoobTheGoober, Skorp, Slackware, Trikk, Wootini, Li0ard, Leeroy.
|
||||
|
||||
### Reverse Engineering Support
|
||||
|
||||
DoobTheGoober, MMX, NeedNotApply, RocketGod, Slackware, Trikk, Li0ard.
|
||||
KAT is developed by **Kara Zajac (.leviathan)**. KAT would not be possible without [ProtoPirate](https://protopirate.net/ProtoPirate/ProtoPirate)—the protocol decoders, reference implementations, and community work are the foundation this tool is built on. I am truly standing on the shoulders of giants.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -12,14 +12,32 @@ This folder describes how each keyfob protocol supported by KAT works. Each docu
|
||||
| Kia V3/V4 | `kia_v3_v4.rs` | [kia_v3_v4.md](kia_v3_v4.md) |
|
||||
| Kia V5 | `kia_v5.rs` | [kia_v5.md](kia_v5.md) |
|
||||
| Kia V6 | `kia_v6.rs` | [kia_v6.md](kia_v6.md) |
|
||||
| Kia V7 | `kia_v7.rs` | [kia_v7.md](kia_v7.md) |
|
||||
| Ford V0 | `ford_v0.rs` | [ford_v0.md](ford_v0.md) |
|
||||
| Ford V1 | `ford_v1.rs` | [ford_v1.md](ford_v1.md) |
|
||||
| Ford V2 | `ford_v2.rs` | [ford_v2.md](ford_v2.md) |
|
||||
| Ford V3 | `ford_v3.rs` | [ford_v3.md](ford_v3.md) |
|
||||
| Honda Static | `honda_static.rs` | [honda_static.md](honda_static.md) |
|
||||
| Honda V1 | `honda_v1.rs` | [honda_v1.md](honda_v1.md) |
|
||||
| Chrysler V0 | `chrysler_v0.rs` | [chrysler_v0.md](chrysler_v0.md) |
|
||||
| Subaru | `subaru.rs` | [subaru.md](subaru.md) |
|
||||
| Toyota | `toyota.rs` | [toyota.md](toyota.md) |
|
||||
| VAG | `vag.rs` | [vag.md](vag.md) |
|
||||
| Fiat V0 | `fiat_v0.rs` | [fiat_v0.md](fiat_v0.md) |
|
||||
| Fiat V1 | `fiat_v1.rs` | [fiat_v1.md](fiat_v1.md) |
|
||||
| Mazda V0 | `mazda_v0.rs` | [mazda_v0.md](mazda_v0.md) |
|
||||
| Mazda Siemens | `mazda_siemens.rs` | [mazda_siemens.md](mazda_siemens.md) |
|
||||
| Mitsubishi V0 | `mitsubishi_v0.rs` | [mitsubishi_v0.md](mitsubishi_v0.md) |
|
||||
| Land Rover V0 | `land_rover_v0.rs` | [land_rover_v0.md](land_rover_v0.md) |
|
||||
| Land Rover RKE | `land_rover_rke.rs` | [land_rover_rke.md](land_rover_rke.md) |
|
||||
| BMW CAS4 | `bmw_cas4.rs` | [bmw_cas4.md](bmw_cas4.md) |
|
||||
| Porsche Touareg | `porsche_touareg.rs` | [porsche_touareg.md](porsche_touareg.md) |
|
||||
| Porsche Cayenne | `porsche_cayenne.rs` | [porsche_cayenne.md](porsche_cayenne.md) |
|
||||
| Suzuki | `suzuki.rs` | [suzuki.md](suzuki.md) |
|
||||
| Scher-Khan | `scher_khan.rs` | [scher_khan.md](scher_khan.md) |
|
||||
| Star Line | `star_line.rs` | [star_line.md](star_line.md) |
|
||||
| PSA | `psa.rs` | [psa.md](psa.md) |
|
||||
| PSA2 (PSA OLD) | `psa2.rs` | [psa2.md](psa2.md) |
|
||||
| KeeLoq generic (fallback) | `keeloq_generic.rs` | [keeloq_generic.md](keeloq_generic.md) |
|
||||
|
||||
**KeeLoq generic** is not a registered decoder; it runs when no protocol matches and tries KeeLoq with every keystore manufacturer key (using `keeloq_common`). Successful decodes appear as **Keeloq (*keystore name*)**.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
title: KAT — Keyfob Analysis Toolkit
|
||||
description: Terminal-based RF signal analysis for automotive keyfobs
|
||||
theme: jekyll-theme-minimal
|
||||
show_downloads: false
|
||||
exclude:
|
||||
- README.md
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# BMW CAS4 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/bmw_cas4.rs`
|
||||
**Reference:** `Flipper-ARF lib/subghz/protocols/bmw_cas4.c`
|
||||
|
||||
## Overview
|
||||
|
||||
BMW CAS4 uses Manchester encoding at 500/1000 µs. 64 bits (8 bytes), AM/OOK. The CAS4 rolling cipher's
|
||||
manufacturer key is not available, so the encrypted portion is left as-is — the frame is only framed and
|
||||
validated, not decrypted. Emission is gated on two fixed marker bytes (`byte[0] == 0x30` and
|
||||
`byte[6] == 0xC5`), which makes the protocol specific and prevents false matches.
|
||||
|
||||
The Manchester decoder uses Flipper's `manchester_decoder.h` transition table, with polarity
|
||||
`level ? Low : High` (the same mapping as Ford V0 / common).
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|----------------|---------|-----------------------------|
|
||||
| Short | 500 µs | ±150 µs (te_delta) |
|
||||
| Long | 1000 µs | ±150 µs |
|
||||
| Preamble pulse | 300–700 µs | |
|
||||
| Gap | ≥1800 µs | separates preamble from data |
|
||||
| Min bits | 64 | 8 bytes |
|
||||
| Preamble | ≥10 pulses | |
|
||||
|
||||
## Frame Layout (64 bits / 8 bytes)
|
||||
|
||||
- **byte 0:** fixed marker `0x30`
|
||||
- **bytes 1–3:** serial (24-bit)
|
||||
- **byte 5:** counter
|
||||
- **byte 6:** fixed marker `0xC5`
|
||||
- **byte 7:** button
|
||||
|
||||
The CAS4 rolling cipher is left undecrypted; the two markers serve as the integrity gate.
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** Manchester (Flipper table, polarity `level ? Low : High`)
|
||||
- **RF modulation:** AM/OOK
|
||||
- **Encryption:** CAS4 rolling cipher left undecrypted (no manufacturer key); gated on fixed markers `0x30`/`0xC5`
|
||||
- **Frequencies:** 433.92 MHz only
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** — begin on a HIGH preamble pulse within the 300–700 µs window.
|
||||
2. **Preamble** — count preamble pulses; a long LOW gap (≥1800 µs) with ≥10 pulses enters Data.
|
||||
3. **Data** — Manchester-decode 64 bits MSB-first; at the 64th bit, require `byte[0]==0x30 && byte[6]==0xC5`, build the signal on success, and reset. An out-of-range pulse aborts.
|
||||
|
||||
## Encoder
|
||||
|
||||
Not supported (the reference encoder is a non-functional stub: `yield` returns reset, `deserialize`
|
||||
returns error). Decode-only.
|
||||
|
||||
## Validation
|
||||
|
||||
Verified by a synthetic-frame check (no local capture available): a unit test Manchester-encodes a frame
|
||||
by driving the decoder's own transition table (the Flipper table is differential, so there is no
|
||||
fixed per-bit pattern), confirms it decodes with the right serial/counter/button, and confirms a frame
|
||||
with wrong marker bytes is rejected.
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Chrysler V0 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/chrysler_v0.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/chrysler_v0.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Chrysler/Dodge/Jeep keyfobs. PWM with a short HIGH pulse and two long-LOW symbols: a "1" payload bit is
|
||||
HIGH ≈600 µs + LOW ≈3400 µs; a "0" payload bit is HIGH ≈300 µs + LOW ≈3700 µs. ~24 preamble pairs
|
||||
(short HIGH + long_b LOW) precede each frame. 80-bit frame: the first 64 bits are `decode_data` (payload
|
||||
bytes 0–7), the last 16 bits are `data_2` (payload bytes 8,9). The 80-bit frame exceeds u64, so `data`
|
||||
reports the most-significant 64 bits and `data_count_bit = 80`.
|
||||
|
||||
Crypto is a proprietary seed-XOR (ported exactly from the reference): `seed = reverse6(key[0] >> 2)` (a
|
||||
6-bit reversed counter); `transform_block` XORs all 9 transformed bytes with `xor_table[seed & 0x0F]`,
|
||||
with an extra nibble flip when the Lock button is set. Dual payload: A (seed even, carries serial +
|
||||
counter) / B (seed odd, carries serial). The frame is gated on a structural `check_ok`, so it does not
|
||||
false-match; `crc_valid` reflects that check.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-------------|---------|------------------------|
|
||||
| Short HIGH | 300 µs | ±150 µs (te_delta) |
|
||||
| One-short HIGH | 600 µs | "1" bit HIGH width |
|
||||
| Long LOW A | 3400 µs | ±400 µs (long_delta) |
|
||||
| Long LOW B | 3700 µs | ±400 µs |
|
||||
| Gap | 8000 µs | te_gap |
|
||||
| Frame gap | 15600 µs| |
|
||||
| Min bits | 80 | |
|
||||
| Preamble | 24 pairs| |
|
||||
|
||||
## Frame Layout (80 bits / 10 payload bytes)
|
||||
|
||||
- **byte 0:** `(reverse6(counter) << 2) | header_low2` — the seed/counter byte
|
||||
- **bytes 1–9:** transformed (XOR-masked) payload; after `transform_block`:
|
||||
- Payload A (even seed): serial = decoded[0..3], rolling counter
|
||||
- Payload B (odd seed): serial = decoded[0..2] + decoded[7]
|
||||
- Button: Lock=1, Unlock=2 (derived from structural relationships between key bytes)
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** PWM (short HIGH + dual long LOW)
|
||||
- **RF modulation:** AM
|
||||
- **Encryption:** proprietary seed-XOR (`transform_block` + `xor_table`); gated on structural `check_ok`
|
||||
- **Frequencies:** 315 MHz, 433.92 MHz
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** — a short HIGH pulse begins the seek.
|
||||
2. **Seek** — count preamble pairs (short HIGH + long LOW); after >15 pairs, a non-short HIGH transitions to Data.
|
||||
3. **Data** — classify each HIGH/LOW pair into a payload bit (`bit ^ 1`); collect 64 bits into `decode_data` then 16 into `data_2`. At 80 bits (or on a terminating gap with >0x4F bits) run `decode_packet`, check `check_ok`, and emit.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported (ENABLE_EMULATE_FEATURE). Rebuilds the 10-byte payload, re-derives seed/plaintext, applies the
|
||||
requested button, then emits preamble + dual 80-bit PWM frames (A and B) separated by frame gaps.
|
||||
|
||||
## Validation
|
||||
|
||||
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available). Unit tests
|
||||
cover `reverse6` involution, `transform_block` invertibility, and a full payload-A round trip.
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Fiat V0 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/fiat_v0.rs`
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Fiat V1 Protocol (Magneti Marelli BSI / PCF7946)
|
||||
|
||||
**Rust module:** `src/protocols/fiat_v1.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/fiat_v1.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Fiat V1 is the Magneti Marelli BSI keyfob protocol (PCF7946), found on Fiat Panda, Grande Punto, and possibly other Fiat/Lancia/Alfa ~2003-2012. Uses Manchester encoding with auto-detected timing from preamble pulse averaging. Two timing variants: Type A (~260 us, e.g. Panda) and Type B (~100 us, e.g. Grande Punto). This is a different protocol from Fiat V0.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Default | Notes |
|
||||
|-----------|---------|-------|
|
||||
| Short | 260 us | Auto-detected from preamble |
|
||||
| Long | 520 us | 2x detected TE |
|
||||
| Delta | 80 us | Minimum 30 us |
|
||||
| Preamble | 80+ pulses | 50-350 us range |
|
||||
| Min bits | 80 | Max 104 bits |
|
||||
|
||||
### Auto-Detection
|
||||
|
||||
Preamble pulses (50-350 us) are accumulated. After 80+ pulses, `te_detected = te_sum / te_count` becomes the reference TE. Type A/B boundary is at 180 us.
|
||||
|
||||
## Frame Layout (80-104 bits = 10-13 bytes)
|
||||
|
||||
| Bytes | Content |
|
||||
|-------|---------|
|
||||
| 0-1 | Preamble residue (0xFFFF/0xFFFC) |
|
||||
| 2-5 | Serial (32 bits) |
|
||||
| 6 | Button:4 \| Epoch:4 |
|
||||
| 7 | Counter:5 \| Scramble:2 \| Fixed:1 |
|
||||
| 8-12 | Encrypted payload (40 bits) |
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** -- Wait for HIGH pulse in 50-350 us range. Also detect retransmission gaps (>5000 us).
|
||||
2. **Preamble** -- Count pulses, accumulate for TE averaging. On gap >= te_detected * 4, transition to Sync.
|
||||
3. **Sync** -- Expect HIGH sync pulse of te_detected * 4 to te_detected * 12 duration.
|
||||
4. **Data** -- Manchester decode up to 104 bits into 13-byte raw buffer.
|
||||
5. **RetxSync** -- After gap >5000 us, look for sync pulse 400-2800 us (retransmission).
|
||||
|
||||
## Buttons
|
||||
|
||||
| Code | Name |
|
||||
|------|--------|
|
||||
| 0x7 | Lock |
|
||||
| 0xB | Unlock |
|
||||
| 0xD | Trunk |
|
||||
|
||||
## Encoder
|
||||
|
||||
Not supported (decode-only).
|
||||
|
||||
## Frequencies
|
||||
|
||||
433.92 MHz.
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Ford V0 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/ford_v0.rs`
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Ford V1 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/ford_v1.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/ford_v1.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Ford V1 uses Manchester encoding at 65/130 µs. 136 bits / 17 bytes: key1 (bytes 0–6, 56 bits) + key2
|
||||
(bytes 7–14, 64 bits) + CRC16 (bytes 15–16). Preamble ≥50 long pulses, then a short-pulse sync window
|
||||
(`sync_event_count > 2`) replays buffered Manchester events and enters the 17-byte data collection. This
|
||||
is a ROLLING-code protocol.
|
||||
|
||||
Crypto: a proprietary parity-based descrambling cipher operating on the 9-byte air block `raw[6..15]`,
|
||||
plus CRC16/CCITT (poly `0x1021`, init `0x0000`) over `raw[3..15]`. Emission is gated on CRC16 validity
|
||||
(with a 17-byte bit-inverted fallback) so it never false-matches. A strict branch
|
||||
(`decoded[3]==raw[5] && decoded[4]==raw[6]`) yields plaintext serial/button/counter; otherwise it is
|
||||
classified as encrypted/rolling (device id only). The Manchester transition table is the Flipper
|
||||
differential-Manchester table (same as Ford V0/V2).
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-------------|--------|------------------------------|
|
||||
| Short | 65 µs | ±39 µs (te_delta) |
|
||||
| Long | 130 µs | ±39 µs (preamble uses ±40) |
|
||||
| Min bits | 136 | 17 bytes |
|
||||
| Preamble | ≥50 long pulses | |
|
||||
|
||||
## Frame Layout (136 bits / 17 bytes)
|
||||
|
||||
- **bytes 0–6:** key1 (56 bits, big-endian) → `DecodedSignal.data`
|
||||
- **bytes 6–14:** air block (9 bytes) — descrambled to plaintext
|
||||
- **bytes 15–16:** CRC16/CCITT over bytes 3–14
|
||||
|
||||
Plaintext fields (when the strict branch matches): serial = `plain[1..3] + plain[0]`, button = `plain[5]>>4`
|
||||
(Sync=0, Lock=1, Unlock=2, Trunk=4, Panic=8), counter = `((plain[5] & 0x0F) << 16) | plain[6..7]`. The
|
||||
`extra` word stashes the CRC16, the strict flag, and `plain[4]` so the encoder can rebuild the full frame.
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** Manchester (Flipper differential table)
|
||||
- **RF modulation:** FM
|
||||
- **Encryption:** proprietary parity descramble cipher + CRC16/CCITT gate; rolling code
|
||||
- **Frequencies:** 315 MHz, 433.92 MHz
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** — a long LOW pulse begins the preamble (count = 1).
|
||||
2. **Preamble** — count long pulses; after ≥50, a short pulse enters Sync.
|
||||
3. **Sync** — buffer short/long events; once `sync_event_count > 2`, replay the buffered events into Manchester and enter Data.
|
||||
4. **Data** — Manchester-decode and pack 17 bytes; at the 17th byte run `process_data` (CRC16 gate + descramble + field extraction) and emit on a valid CRC.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported (6 bursts). Faithful re-encode is only possible when the original frame's plaintext was
|
||||
recovered (strict branch); the encoder rebuilds the plaintext from fields + the stashed `plain[4]`,
|
||||
re-derives the air block + CRC16, and emits 6 bursts of a 400-pair long preamble + sync + 136 Manchester
|
||||
bits, with a per-burst `pkt[4]` override.
|
||||
|
||||
## Validation
|
||||
|
||||
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available). Unit tests
|
||||
cover a CRC16/XMODEM known vector, the descramble round trip, the strict-branch decode, and a full
|
||||
encode→decode at the on-air-frame level.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Ford V2 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/ford_v2.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/ford_v2.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Ford V2 uses Manchester encoding at 200/400 µs. 104 bits total (13 bytes). The frame begins with a
|
||||
16-bit Manchester sync that equals `0x7FA7` (the decoder matches the *inverted* shift register against
|
||||
`!0x7FA7`); those two sync bytes `0x7F 0xA7` head the 13-byte buffer. Decoded data bits are inverted
|
||||
before packing (`data_bit = !bit`). There is no CRC — structure is validated by the two sync bytes plus
|
||||
a known button code, so Ford V2 will not false-match.
|
||||
|
||||
The Manchester decoder uses Flipper's transition table (same table as Ford V0), with Ford V2 polarity
|
||||
`level ? High : Low`.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-----------|--------|-----------------------------|
|
||||
| Short | 200 µs | ±260 µs (te_delta) |
|
||||
| Long | 400 µs | ±260 µs |
|
||||
| Min bits | 104 | 13 bytes |
|
||||
| Preamble | ≥64 short pulses | before the sync |
|
||||
|
||||
## Frame Layout (104 bits / 13 bytes)
|
||||
|
||||
- **bytes 0–1:** sync `0x7F 0xA7`
|
||||
- **bytes 2–5:** serial (32-bit, big-endian)
|
||||
- **byte 6:** button — valid codes `0x10` (Lock), `0x11` (Unlock), `0x13` (Trunk), `0x14` (Panic), `0x15`
|
||||
- **bytes 7–8 + byte 9 MSB:** counter = `((b7 & 0x7F) << 9) | (b8 << 1) | (b9 >> 7)`; byte 7 MSB carries a button parity bit (refreshed by the encoder)
|
||||
- **bytes 9–12:** rolling/hop tail (stashed in `extra` so the encoder can rebuild the full frame)
|
||||
|
||||
The exported `data` (Key) word is the top 8 bytes; bytes 8–12 (40 bits) are carried in `extra`.
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** Manchester (Ford V2 polarity `level ? High : Low`; decoded bits inverted)
|
||||
- **RF modulation:** FM (per ProtoPirate `SubGhzProtocolFlag_FM`)
|
||||
- **Encryption:** none — validated by sync bytes + valid button code
|
||||
- **Frequencies:** 315 MHz, 433.92 MHz
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** → a short pulse (~200 µs) begins the preamble.
|
||||
2. **Preamble** — count short pulses; after ≥64 shorts a long LOW pulse enters Sync.
|
||||
3. **Sync** — Manchester-decode bits into a 16-bit shift register; when it matches `!0x7FA7`, prime the two sync bytes and enter Data.
|
||||
4. **Data** — Manchester events feed the state machine; decoded bits are inverted and packed MSB-first into 13 bytes. At 104 bits the sync bytes and button are validated and the frame committed. A non-short/long pulse (gap) ends the attempt.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported (matches `subghz_protocol_encoder_ford_v2`). Rebuilds the 13-byte frame, applies the requested
|
||||
button, refreshes byte-7 parity, then emits 6 bursts of a 70-pair preamble + sync + 104 Manchester bits
|
||||
(encoder short 240 µs), separated by ~16 ms inter-burst gaps.
|
||||
|
||||
## Validation
|
||||
|
||||
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available).
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Ford V3 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/ford_v3.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/ford_v3.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Ford V3 uses Manchester encoding at 240/480 µs. 104 bits total (13 bytes), transmitted as plaintext —
|
||||
there is no CRC or encryption. Decode-only: the ProtoPirate reference ships a NULL encoder, so KAT
|
||||
does not transmit Ford V3 (Replay still works on the raw capture).
|
||||
|
||||
The Manchester decoder uses Flipper's `manchester_decoder.h` transition table (same table as Ford V0),
|
||||
but Ford V3 maps the level the opposite way from Ford V0: `level ? High : Low`.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-----------|--------|--------------------|
|
||||
| Short | 240 µs | ±60 µs (te_delta) |
|
||||
| Long | 480 µs | ±60 µs |
|
||||
| Min bits | 104 | 13 bytes |
|
||||
| Preamble | ≥30 short pulses | before first long |
|
||||
|
||||
## Frame Layout (104 bits / 13 bytes)
|
||||
|
||||
- **byte 0:** header
|
||||
- **bytes 1–4:** serial (32-bit, big-endian)
|
||||
- **byte 5:** hop/reserved
|
||||
- **byte 6:** button — bit 0 set → Unlock, else Lock
|
||||
- **bytes 7–8:** counter, stored bitwise-inverted (`~b[7]`, `~b[8]`)
|
||||
- **bytes 9–12:** rolling/hop tail (not parsed)
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** — a short pulse (~240 µs) starts the preamble (count = 1).
|
||||
2. **Preamble** — count short pulses; once ≥30 shorts are seen, a long pulse begins the data
|
||||
(Manchester state seeded to Mid1, first bit decoded from the long pulse).
|
||||
3. **Data** — Manchester events (short/long × level) feed the state machine; bits pack MSB-first into
|
||||
13 bytes. At 104 bits the fields are parsed and the signal is emitted. A non-short/non-long pulse
|
||||
(gap) ends the frame.
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** Manchester
|
||||
- **RF modulation:** FM (per ProtoPirate `SubGhzProtocolFlag_FM`)
|
||||
- **Encryption:** none (plaintext)
|
||||
- **Frequencies:** 315 MHz, 433.92 MHz
|
||||
|
||||
## Encoder
|
||||
|
||||
Not supported (reference encoder is NULL). Decode-only.
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Honda Static Protocol
|
||||
|
||||
**Rust module:** `src/protocols/honda_static.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/honda_static.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Honda/Acura fixed-code keyfobs. 64-bit frame. Unlike most KAT decoders, Honda Static does NOT use the
|
||||
Flipper `manchester_decoder.h` transition table: it buffers a per-element *symbol stream* (one bit per
|
||||
~63 µs element) and then performs a custom Manchester unpack over symbol pairs. A short pulse contributes
|
||||
one symbol equal to the pulse level; a long pulse contributes two symbols of the same level. Anything
|
||||
outside both ranges (e.g. the 700 µs sync or a trailing gap) terminates the buffer and triggers a parse.
|
||||
|
||||
The checksum is an XOR of the first 7 packet bytes. Emission is gated on the checksum, so Honda Static
|
||||
will not false-match. The parser tries the inverted-Manchester interpretation first (what the encoder
|
||||
emits), then non-inverted forward, then a bit-reversed-bytes pass.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|----------------|--------|--------------------------------|
|
||||
| Element | 63 µs | one symbol per element |
|
||||
| Short pulse | 28–98 µs | base 28 µs, span 70 µs |
|
||||
| Long pulse | 61–191 µs | base 61 µs, span 130 µs (two symbols) |
|
||||
| Sync | 700 µs | terminates the symbol buffer |
|
||||
| Min symbols | 36 | before a parse is attempted |
|
||||
| Min bits | 64 | |
|
||||
|
||||
(Reported timing profile: te_short 63 µs, te_long 700 µs, te_delta 120 µs.)
|
||||
|
||||
## Frame Layout (64 bits, MSB-first into 8 bytes)
|
||||
|
||||
- **bits 0–3:** button (4-bit) — Lock=1, Unlock=2, Trunk=4, Remote Start=5, Panic=8, Lock×2=9
|
||||
- **bits 4–31:** serial (28-bit)
|
||||
- **bits 32–55:** counter (24-bit)
|
||||
- **bits 56–63:** checksum (XOR of bytes 0–6)
|
||||
|
||||
The exported `data` (Key) word is the C `generic.data`: a compact nibble-packed layout, NOT the raw
|
||||
decoded packet bytes. The KAT counter field is the low 16 bits of the 24-bit counter.
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** custom symbol-stream Manchester over ~63 µs elements
|
||||
- **RF modulation:** FM
|
||||
- **Encryption:** none — gated on the XOR checksum + valid button + valid serial (serial ≠ 0 and ≠ 0x0FFFFFFF)
|
||||
- **Frequencies:** 315 MHz, 433.92 MHz
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **feed** — classify each pulse: short → 1 symbol, long → 2 symbols; buffer them.
|
||||
2. On an out-of-range pulse (700 µs sync or gap), if ≥36 symbols are buffered, walk past the alternating preamble + sync run, Manchester-unpack 64 bits over symbol pairs (inverted first, then non-inverted), validate, and emit. Then clear the buffer.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported (matches `honda_static_build_upload`). Emits a 700 µs HIGH sync, a 160-element alternating
|
||||
preamble at 63 µs, 64 data bits (each as `!value`/`value` at 63 µs), and a trailing 700 µs sync.
|
||||
|
||||
## Validation
|
||||
|
||||
Decodes REAL IMPORTS captures (IMPORTS/honda Lock and Unlock). Also covered by encode→decode round-trip
|
||||
and packet-validate unit tests.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Honda V1 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/honda_v1.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/honda_v1.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Honda/Acura fixed-code keyfobs — a DIFFERENT protocol from `honda_static`. The on-wire frame carries 68
|
||||
bits: 64-bit data + a 4-bit CRC-fold nibble. Encoding is short/long pulse PWM, glued together by a
|
||||
"pending bit" timing accumulator before classification: sub-`te_delta` runts are summed into `pending`;
|
||||
a HIGH level extends the running HIGH pulse; a LOW flushes the accumulated HIGH (if it reached
|
||||
`te_short_min`) as a synthetic symbol, then classifies the LOW.
|
||||
|
||||
Validation is a button-code table (Unlock=0, Lock=8, Trunk=9, Panic=10) plus a CRC-fold checksum.
|
||||
Emission is gated on the button being valid; `crc_valid` reflects whether the received CRC nibble matches
|
||||
either wire-order checksum. The strong button gate keeps Honda V1 from false-matching.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-------------|---------|-----------------------------|
|
||||
| Short | 1000 µs | ±400 µs (te_delta) |
|
||||
| Long | 2000 µs | ±400 µs |
|
||||
| Short min | 600 µs | flush threshold for HIGH |
|
||||
| End gap | 3500 µs | >te_end commits the frame |
|
||||
| Min bits | 68 | |
|
||||
|
||||
## Frame Layout (68 bits = 64-bit data + 4-bit CRC nibble)
|
||||
|
||||
After the end gap, `commit` left-shifts the 12-byte bit buffer to drop leading preamble leakage and align
|
||||
the trailing 68-bit frame. `data` = first 8 bytes (64 bits); the CRC nibble = byte 8's high nibble.
|
||||
|
||||
- **data[63:36]:** serial (28-bit)
|
||||
- **data[31:28]:** button (nibble) — Unlock=0, Lock=8, Trunk=9, Panic=10
|
||||
- **data[15:0]:** counter (16-bit)
|
||||
- **CRC nibble:** CRC-fold checksum (`honda_v1_checksum*`), validated against either wire-order value
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** short/long pulse PWM with pending-bit timing accumulation
|
||||
- **RF modulation:** AM (OOK)
|
||||
- **Encryption:** none — gated on the button-code table + CRC-fold checksum
|
||||
- **Frequencies:** 315 MHz, 433.92 MHz
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **feed** — accumulate sub-`te_delta` runts into `pending`; flush HIGH pulses and classify LOW pulses into symbols.
|
||||
2. **symbol** — Reset → Preamble (short/long pulses, needs a long + >5 count) → Data.
|
||||
3. **Data** — pending-bit accumulation: a short pulse toggles `data_pending` and emits a bit when paired; a long pulse emits directly. After a >te_end gap, commit: align the 68-bit frame, validate the button, and emit.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported (ENABLE_EMULATE_FEATURE). Builds the 64-bit key from serial/button/counter via the button code
|
||||
table, then emits a 180-element short-pair preamble + 4 PWM frames (2 per checksum wire value).
|
||||
|
||||
## Validation
|
||||
|
||||
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available). Unit tests
|
||||
cover the key/field round trip, full encode→decode, and the button-validity mask.
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KAT — Keyfob Analysis Toolkit</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1>KAT — Keyfob Analysis Toolkit</h1>
|
||||
<p class="tagline">Terminal-based RF signal analysis for automotive keyfobs</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="navbar">
|
||||
<div class="container">
|
||||
<ul>
|
||||
<li><a href="#features">Features</a></li>
|
||||
<li><a href="#supported-protocols">Protocols</a></li>
|
||||
<li><a href="#requirements">Requirements</a></li>
|
||||
<li><a href="#documentation">Docs</a></li>
|
||||
<li><a href="https://github.com/KaraZajac/KAT" target="_blank">GitHub</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<h2>Capture, Decode & Analyze Keyfob Signals</h2>
|
||||
<p>A real-time terminal UI for RF signal analysis with support for HackRF One and RTL-SDR hardware.</p>
|
||||
<div class="cta-buttons">
|
||||
<a href="https://github.com/KaraZajac/KAT#installation" class="btn btn-primary">Get Started</a>
|
||||
<a href="https://github.com/KaraZajac/KAT" class="btn btn-secondary">View on GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="features" class="features">
|
||||
<div class="container">
|
||||
<h2>Key Features</h2>
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<h3>📡 Real-Time Capture</h3>
|
||||
<p>Receive and demodulate AM/OOK keyfob signals at configurable frequencies with HackRF or RTL-SDR</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🔓 Multi-Protocol Decoding</h3>
|
||||
<p>18 protocol decoders including Kia V0–V6, Ford, Fiat, Mazda, Mitsubishi, Porsche, Subaru, Suzuki, VAG, PSA, and more</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🔑 KeeLoq Keystore</h3>
|
||||
<p>Generic KeeLoq fallback with embedded manufacturer keys for automatic decoding</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>📤 Signal Retransmission</h3>
|
||||
<p>Transmit Lock, Unlock, Trunk, and Panic commands from decoded signals (HackRF only)</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>💾 Multiple Export Formats</h3>
|
||||
<p>.fob (JSON) and .sub (Flipper Zero compatible) formats for signal storage and sharing</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🔧 Rich Signal Details</h3>
|
||||
<p>View encoding, RF modulation, encryption, serial, counter, key data, CRC, and raw timing data</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="supported-protocols" class="protocols">
|
||||
<div class="container">
|
||||
<h2>Supported Protocols</h2>
|
||||
<div class="protocol-list">
|
||||
<div class="protocol-column">
|
||||
<h3>Kia</h3>
|
||||
<ul>
|
||||
<li>Kia V0</li>
|
||||
<li>Kia V1</li>
|
||||
<li>Kia V2</li>
|
||||
<li>Kia V3 / V4</li>
|
||||
<li>Kia V5</li>
|
||||
<li>Kia V6</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="protocol-column">
|
||||
<h3>Other Manufacturers</h3>
|
||||
<ul>
|
||||
<li>Ford V0</li>
|
||||
<li>Fiat V0 / V1</li>
|
||||
<li>Mazda V0</li>
|
||||
<li>Mitsubishi V0</li>
|
||||
<li>Porsche Touareg</li>
|
||||
<li>Subaru</li>
|
||||
<li>Suzuki</li>
|
||||
<li>VAG (VW/Audi/Seat/Skoda)</li>
|
||||
<li>PSA (Peugeot/Citroën)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="protocol-column">
|
||||
<h3>Generic</h3>
|
||||
<ul>
|
||||
<li>Scher-Khan</li>
|
||||
<li>Star Line</li>
|
||||
<li>KeeLoq (Generic)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="requirements" class="requirements">
|
||||
<div class="container">
|
||||
<h2>Requirements</h2>
|
||||
<h3>Hardware</h3>
|
||||
<ul>
|
||||
<li><strong>HackRF One</strong> (or compatible) — Full receive and transmit capabilities</li>
|
||||
<li><strong>RTL433 / RTL-SDR</strong> — Receive-only; no transmit supported</li>
|
||||
</ul>
|
||||
|
||||
<h3>Software</h3>
|
||||
<ul>
|
||||
<li>Rust 1.75+ (for building from source)</li>
|
||||
<li>libhackrf — HackRF C library and headers</li>
|
||||
<li>libusb — For RTL433/RTL-SDR support</li>
|
||||
</ul>
|
||||
|
||||
<h3>Operating System</h3>
|
||||
<p>macOS, Linux, and other Unix-like systems. Precompiled binaries and installation instructions available on GitHub.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="documentation" class="documentation">
|
||||
<div class="container">
|
||||
<h2>Protocol Documentation</h2>
|
||||
<p>Detailed protocol specifications and decoder documentation:</p>
|
||||
<div class="doc-links">
|
||||
<a href="kia_v0.html" class="doc-link">Kia V0</a>
|
||||
<a href="kia_v1.html" class="doc-link">Kia V1</a>
|
||||
<a href="kia_v2.html" class="doc-link">Kia V2</a>
|
||||
<a href="kia_v3_v4.html" class="doc-link">Kia V3/V4</a>
|
||||
<a href="kia_v5.html" class="doc-link">Kia V5</a>
|
||||
<a href="kia_v6.html" class="doc-link">Kia V6</a>
|
||||
<a href="ford_v0.html" class="doc-link">Ford V0</a>
|
||||
<a href="fiat_v0.html" class="doc-link">Fiat V0</a>
|
||||
<a href="fiat_v1.html" class="doc-link">Fiat V1</a>
|
||||
<a href="mazda_v0.html" class="doc-link">Mazda V0</a>
|
||||
<a href="mitsubishi_v0.html" class="doc-link">Mitsubishi V0</a>
|
||||
<a href="porsche_touareg.html" class="doc-link">Porsche Touareg</a>
|
||||
<a href="subaru.html" class="doc-link">Subaru</a>
|
||||
<a href="suzuki.html" class="doc-link">Suzuki</a>
|
||||
<a href="vag.html" class="doc-link">VAG</a>
|
||||
<a href="psa.html" class="doc-link">PSA</a>
|
||||
<a href="scher_khan.html" class="doc-link">Scher-Khan</a>
|
||||
<a href="star_line.html" class="doc-link">Star Line</a>
|
||||
<a href="keeloq_generic.html" class="doc-link">KeeLoq (Generic)</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="security-notice">
|
||||
<div class="container">
|
||||
<h3>⚠️ Security and Legal Disclaimer</h3>
|
||||
<p>Use KAT only on systems and vehicles you own or have explicit, written permission to test. Capturing, decoding, or transmitting keyfob and vehicle-access signals without authorization may be illegal in your jurisdiction. You are solely responsible for ensuring your use complies with applicable laws. KAT is intended for security research, authorized penetration testing, education, and legitimate testing on your own equipment.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2024 KAT — Keyfob Analysis Toolkit. Licensed under the terms in LICENSE file. <a href="https://github.com/KaraZajac/KAT" target="_blank">View on GitHub</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# KeeLoq Generic Fallback
|
||||
|
||||
**Rust module:** `src/protocols/keeloq_generic.rs`
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Kia V0 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/kia_v0.rs`
|
||||
|
||||
+6
-2
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Kia V1 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/kia_v1.rs`
|
||||
@@ -5,7 +9,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
Kia V1 uses Manchester encoding at 800/1600 µs. 57 bits total: 32 serial + 8 button + 12 counter + 4 CRC. Long preamble (~90 long pairs). CRC4 with offset rules (cnt_high 0 vs ≥ 6).
|
||||
Kia V1 uses Manchester encoding at 800/1600 us. 57 bits total: 32 serial + 8 button + 12 counter + 4 CRC. Long preamble (~90 long pairs). CRC4 uses XOR of nibbles over 7 bytes (serial + button + cnt_low + cnt_high) with offset 1.
|
||||
|
||||
## Timing
|
||||
|
||||
@@ -24,7 +28,7 @@ Manchester: symbol duration short or long; bit value from transition direction.
|
||||
- 32 bits: serial
|
||||
- 8 bits: button
|
||||
- 12 bits: counter
|
||||
- 4 bits: CRC4 (checksum with offset rules)
|
||||
- 4 bits: CRC4 (XOR of nibbles, 7 bytes, offset 1)
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Kia V2 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/kia_v2.rs`
|
||||
|
||||
+5
-1
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Kia V3/V4 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/kia_v3_v4.rs`
|
||||
@@ -39,4 +43,4 @@ Supported; 3 bursts, 10 s inter-burst gap; preamble, sync, encrypted payload + C
|
||||
|
||||
## Keystore
|
||||
|
||||
Requires KIA manufacturer key (keystore type 10, `kia_mf_key`). Loaded from embedded blob or keystore.ini.
|
||||
Requires KIA manufacturer key (keystore type 10, `kia_mf_key`). Loaded from the embedded keystore (built from `REFERENCES/mf_keys.txt`).
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Kia V5 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/kia_v5.rs`
|
||||
|
||||
+6
-2
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Kia V6 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/kia_v6.rs`
|
||||
@@ -26,7 +30,7 @@ Manchester (event mapping 0/2/4/6 for level convention). Three 64/64/16-bit segm
|
||||
- Part2: 64 bits (inverted)
|
||||
- Part3: 16 bits (inverted)
|
||||
|
||||
AES-128 decrypt with key = f(keystore_a, keystore_b, XOR_MASK_LOW, XOR_MASK_HIGH). Serial/button/counter and CRC extracted after decryption.
|
||||
AES-128 decrypt with key = f(keystore_a, keystore_b, XOR_MASK_LOW, XOR_MASK_HIGH). Serial/button/counter, CRC, and fx_field extracted after decryption. The fx_field is derived from the top 2 bytes of stored_part1_high and stored in `DecodedSignal.extra`.
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
@@ -36,7 +40,7 @@ AES-128 decrypt with key = f(keystore_a, keystore_b, XOR_MASK_LOW, XOR_MASK_HIGH
|
||||
|
||||
## Encoder
|
||||
|
||||
Decode-only in KAT (no encoder in reference).
|
||||
Supported. Ported from ProtoPirate (`ENABLE_EMULATE_FEATURE`). Builds plaintext (fx_field, serial, button, counter, S-box CRC, CRC8), AES-128 encrypts, packs into 3 parts, then Manchester encodes with two-pass preamble (640 pairs + data, gap, 38 pairs + data). Requires fx_field from decoded signal's `extra` field.
|
||||
|
||||
## Frequencies
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Kia V7 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/kia_v7.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/kia_v7.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Kia V7 uses Manchester encoding at 250/500 µs. 64 bits. The decoded 64-bit word is bit-inverted
|
||||
(`~data`); a valid frame has a fixed header byte `0x4C` and a CRC8 (poly `0x7F`, init `0x4C`) over bytes
|
||||
0–6. Emission is gated on header + CRC, so Kia V7 is strongly validated and will not false-match.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-----------|--------|---------------------|
|
||||
| Short | 250 µs | ±100 µs (te_delta) |
|
||||
| Long | 500 µs | ±100 µs |
|
||||
| Min bits | 64 | |
|
||||
| Preamble | ≥16 short pairs | before sync |
|
||||
|
||||
## Frame Layout (64 bits / 8 bytes, after inversion)
|
||||
|
||||
- **byte 0:** header `0x4C`
|
||||
- **bytes 1–2:** counter (16-bit, big-endian)
|
||||
- **bytes 3–6:** serial (28-bit) — `(b3<<20)|(b4<<12)|(b5<<4)|(b6>>4)`, masked to 0x0FFFFFFF
|
||||
- **byte 6 low nibble:** button (4-bit) — Lock=1, Unlock=2, Trunk=3, aux=8
|
||||
- **byte 7:** CRC8 over bytes 0–6 (poly `0x7F`, init `0x4C`)
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** Manchester (level ? (H,L) : (L,H); decoded word is bit-inverted)
|
||||
- **RF modulation:** FM (per ProtoPirate `SubGhzProtocolFlag_FM`)
|
||||
- **Encryption:** none — gated on fixed header `0x4C` + CRC8
|
||||
- **Frequencies:** 315 MHz, 433.92 MHz
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** — a short HIGH pulse begins the preamble.
|
||||
2. **Preamble** — count short pairs; once >15 pairs are seen, a long HIGH pulse transitions to SyncLow and preloads four seed bits (1,0,1,1 = the inverted header's top nibble 0xB).
|
||||
3. **SyncLow** — a short LOW after the long sync enters Data.
|
||||
4. **Data** — Manchester-decode the remaining 60 bits. At 64 bits, invert the word, check the header equals `0x4C` and the CRC8 validates, then emit.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported (matches `kia_v7_encode_key`). Rebuilds the 64-bit key from serial/button/counter with the
|
||||
CRC8, then emits a 32-pair short preamble, a merged long sync pulse, 64 Manchester bits, and a trailing
|
||||
gap.
|
||||
|
||||
## Validation
|
||||
|
||||
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available).
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Land Rover RKE Protocol
|
||||
|
||||
**Rust module:** `src/protocols/land_rover_rke.rs`
|
||||
**Reference:** `Flipper-ARF lib/subghz/protocols/landrover_rke.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Ported from the Flipper-ARF firmware (D4C1-Labs), itself derived from Pandora DXL 5000 firmware. Land
|
||||
Rover shares the Ford/Jaguar baseband (firmware protocol ID `0x0E`) but uses a distinct 66-bit frame.
|
||||
Encoding is fixed-width PWM with a 1000 µs bit period: Bit-1 = 700 µs HIGH + 300 µs LOW, Bit-0 = 300 µs
|
||||
HIGH + 700 µs LOW. Preamble = 20× (400 µs HIGH + 600 µs LOW); sync = 400 µs HIGH + 9600 µs LOW.
|
||||
|
||||
KeeLoq: the hop code is the raw 32-bit KeeLoq ciphertext. Full decryption needs the per-fob manufacturer
|
||||
key (provisioned, not in firmware), so KAT exposes the framed fields and leaves the hop encrypted —
|
||||
`crc_valid = false` since no cryptographic check is performed. Emission is still gated tightly on the long
|
||||
preamble run, the distinctive 9.6 ms sync gap, and exactly 66 PWM bits, so this loose-looking PWM decoder
|
||||
does not false-match Kia/Subaru/Ford captures.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|----------------|---------|-----------------------------|
|
||||
| Bit-1 | 700 µs HIGH + 300 µs LOW | ±20% (relative) |
|
||||
| Bit-0 | 300 µs HIGH + 700 µs LOW | ±20% |
|
||||
| Preamble pair | 400 µs HIGH + 600 µs LOW | ×20 |
|
||||
| Sync | 400 µs HIGH + 9600 µs LOW | |
|
||||
| Repeat gap | 12000 µs | |
|
||||
| Min bits | 66 | |
|
||||
| Preamble min | 16 pairs| |
|
||||
|
||||
(Reported timing profile: te_short 300 µs, te_long 700 µs, te_delta 140 µs.)
|
||||
|
||||
## Frame Layout (66 bits, MSB-first)
|
||||
|
||||
- **[65:34]:** 32-bit KeeLoq encrypted hopping code
|
||||
- **[33:10]:** 24-bit fixed fob serial
|
||||
- **[9:6]:** 4-bit button — Lock=0x1, Unlock=0x2, Boot/Tailgate=0x4, Panic=0x8
|
||||
- **[5:2]:** 4-bit function/repeat flags
|
||||
- **[1:0]:** 2-bit status — battery-low=0x1, repeat=0x2
|
||||
|
||||
66 bits do not fit a u64: `data` holds the low 64 frame bits and `extra` holds the top 2 (the high 2 bits
|
||||
of the hop code), so encode round-trips the hop losslessly. The KAT counter field surfaces the low 16
|
||||
bits of the hop ciphertext.
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** fixed-width PWM
|
||||
- **RF modulation:** OOK/AM
|
||||
- **Encryption:** KeeLoq hop left encrypted (no manufacturer key); `crc_valid = false`
|
||||
- **Frequencies:** 433.92 MHz, 315 MHz
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** — wait for the first ~400 µs preamble HIGH.
|
||||
2. **Preamble** — count 400/600 µs pairs; the distinctive ~9600 µs LOW after a ~400 µs HIGH (with ≥16 pairs) enters DataHigh.
|
||||
3. **DataHigh/DataLow** — read each bit from its HIGH/LOW half pair (±20% windows). At 66 bits, build the signal and emit.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported. Reconstructs the original frame to preserve hop code / func bits / status, overrides only the
|
||||
button, and emits 4 repetitions of preamble + sync + 66 MSB-first PWM bits, separated by 12 ms gaps.
|
||||
|
||||
## Validation
|
||||
|
||||
Verified by encode→decode round-trips / synthetic-frame checks (no local capture available). Unit tests
|
||||
cover pack/unpack round trips, encode→decode across multiple serials/hops/buttons, and rejection of
|
||||
truncated frames and wrong sync gaps.
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Land Rover V0 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/land_rover_v0.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/land_rover_v0.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Land Rover V0 uses **differential** Manchester (NOT the Flipper transition table) at 250/500 µs, with a
|
||||
~750 µs sync pulse and a ≥64-pair short preamble. The frame is 81 bits = an 80-bit body (`raw[0..10]`)
|
||||
plus one trailing `extra_bit`. The 64-bit key reported as `data` is `raw[0..8]` big-endian; `raw[8..10]`
|
||||
is a 16-bit `tail`.
|
||||
|
||||
The check is a proprietary 3-bit polynomial over the counter (`calculate_check`); the tail is `0xFFFF`
|
||||
or `0x7FFF` depending on a 1-bit parity of the counter (`calculate_tail`). Emission is gated on the
|
||||
reserved bits being zero, the check matching, the tail matching, and `extra_bit` set — so Land Rover V0
|
||||
is strongly validated and will not false-match.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-------------|--------|------------------------|
|
||||
| Short | 250 µs | ±100 µs (te_delta) |
|
||||
| Long | 500 µs | ±100 µs |
|
||||
| Sync | 750 µs | ±120 µs |
|
||||
| Min bits | 81 | 80-bit body + extra_bit|
|
||||
| Preamble | ≥64 pairs | |
|
||||
|
||||
## Frame Layout (81 bits)
|
||||
|
||||
64-bit key (`raw[0..8]`):
|
||||
|
||||
- **bytes 0–2:** 24-bit command signature — Lock = `0xC20363`, Unlock = `0xA285E3`
|
||||
- **bytes 3–5:** 24-bit serial
|
||||
- **byte 6 + byte 7 MSB:** 9-bit counter = `(b6 << 1) | (b7 >> 7)`
|
||||
- **byte 7 bits 0x78:** 3 reserved bits (must be 0)
|
||||
- **byte 7 bits 0x07:** 3-bit check (`calculate_check`)
|
||||
|
||||
Then `raw[8..10]` = 16-bit tail (`0xFFFF`/`0x7FFF`), and a trailing `extra_bit` (must be 1). The tail is
|
||||
stashed in `extra` for the encoder.
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** differential Manchester
|
||||
- **RF modulation:** FM
|
||||
- **Encryption:** none — gated on the 3-bit check polynomial + tail parity + reserved-bits + extra_bit
|
||||
- **Frequencies:** 315 MHz, 433.92 MHz
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset → PreambleLow/PreambleHigh** — count short pairs (≥64), terminated by a ~750 µs sync HIGH.
|
||||
2. **SyncLow** — a ~750 µs sync LOW seeds the frame (bit 0 = 1) and enters Data.
|
||||
3. **Data** — the differential machine (`process_transition`/`add_decoded_bit`) tracks `previous_bit`, skips the initial boundary short-high pad, and completes short half-bits via `pending_short`. At 81 bits, `finish_frame` validates and emits.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported. Builds the 64-bit key from signature/serial/counter, emits a 319-pair preamble, the sync,
|
||||
differential-Manchester body, the 16-bit tail, and a trailing extra bit. Faithful-port note: the
|
||||
reference encoder forces frame bit 1 = 0, so the Lock signature `0xC20363` (whose bit 1 is 1) is emitted
|
||||
as `0x820363`; the Unlock signature `0xA285E3` round-trips cleanly. KAT reproduces this behaviour.
|
||||
|
||||
## Validation
|
||||
|
||||
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available). Unit tests
|
||||
cover the check polynomial vs. the reference, the tail parity, Unlock round trips across 256 serial/counter
|
||||
values, the Lock forced-bit quirk, and rejection of a corrupted check.
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Mazda Siemens Protocol
|
||||
|
||||
**Rust module:** `src/protocols/mazda_siemens.rs`
|
||||
**Reference:** `Flipper-ARF lib/subghz/protocols/mazda_siemens.c`
|
||||
|
||||
## Overview
|
||||
|
||||
The Siemens/VDO keyfob cipher used on some Mazda vehicles — a DIFFERENT protocol from KAT's existing
|
||||
"Mazda V0" (Pandora) decoder, even though both ride a 250/500 µs pair-based stream at 433.92 MHz FM. The
|
||||
decoder is pair-based: `feed()` ignores `level` and interprets raw durations in pairs (`process_pair`),
|
||||
collecting bits with *inverted* polarity (`state_bit == 0` → stored 1). 64-bit frame.
|
||||
|
||||
Siemens obfuscation (`mazda_xor_deobfuscate`): parity-dependent XOR mask, then bit-deinterleave of bytes
|
||||
5/6. The inner Siemens cipher's plaintext is left as-is (no key); only this obfuscation/interleave layer
|
||||
is reversed. The gate is an additive checksum `sum(data[0..7]) == data[7]`, plus the structural
|
||||
preamble/sync/bit-count constraints, which keeps it from false-matching other 250/500 µs Manchester
|
||||
protocols.
|
||||
|
||||
> **Note:** KAT's existing "Mazda V0" decoder implements the same algorithm and keeps first-match
|
||||
> priority, so on a shared frame Mazda V0 fires first.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-------------|--------|-----------------------------|
|
||||
| Short | 250 µs | ±100 µs (te_delta) |
|
||||
| Long | 500 µs | ±100 µs |
|
||||
| Min bits | 64 | |
|
||||
| Preamble | ≥13 short/short pairs | |
|
||||
| Completion | 80–105 collected bits | |
|
||||
|
||||
## Frame Layout (64 bits / 8 bytes, after deobfuscation)
|
||||
|
||||
The 14-byte buffer's leading sync byte is discarded, leaving 8 bytes:
|
||||
|
||||
- **serial:** `data >> 32` (32-bit)
|
||||
- **button:** `(data >> 24) & 0xFF` — Lock=0x10, Unlock=0x20, Trunk=0x40
|
||||
- **counter:** `(data >> 8) & 0xFFFF` (16-bit)
|
||||
- **byte 7:** additive checksum of bytes 0–6
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** pair-based Manchester (inverted polarity), 250/500 µs
|
||||
- **RF modulation:** FM
|
||||
- **Encryption:** Siemens obfuscation (parity XOR + bit-interleave); gated on the additive checksum
|
||||
- **Frequencies:** 433.92 MHz only
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** — a short pulse begins the preamble.
|
||||
2. **PreambleSave/PreambleCheck** — count short/short pairs (≥13); a short→long transition seeds the leading sync bit (a 1) and enters data.
|
||||
3. **DataSave/DataCheck** — process duration pairs into bits; on a non-matching pair, run `check_completion` (discard sync byte, deobfuscate, validate additive checksum) and emit.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported (`subghz_protocol_encoder_mazda_siemens_get_upload`). Increments the counter byte, recomputes
|
||||
the checksum, obfuscates (interleave + XOR), then emits a 12-byte 0xFF preamble, a 50 ms gap, `0xFF 0xFF
|
||||
0xD7`, the 8 obfuscated bytes transmitted as `255 − byte`, a `0x5A` tail, and a trailing 50 ms gap.
|
||||
Manchester per byte: bit 1 → (H,L), bit 0 → (L,H) at te_short. As in the C, the decoder and encoder are
|
||||
not a clean raw-timing round-trip pair (the encoder targets a TX upload).
|
||||
|
||||
## Validation
|
||||
|
||||
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available). Unit tests
|
||||
cover the obfuscate/deobfuscate XOR+interleave cipher layer (a true inverse, both parity branches), the
|
||||
field layout, and the TX upload smoke test.
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Mazda V0 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/mazda_v0.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/mazda_v0.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Mazda V0 uses a custom pair-based encoding (not standard Manchester). The `level` parameter is ignored; the decoder processes raw duration pairs. Preamble: 13+ short/short pairs followed by a short+long transition into data. Data bits are collected using a prev_state tracker with inverted polarity, then XOR-deobfuscated, checksum-validated, and parsed.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-----------|--------|--------------|
|
||||
| Short | 250 us | +/-100 us |
|
||||
| Long | 500 us | +/-100 us |
|
||||
| Min bits | 64 | |
|
||||
| Completion| 80-105 bits | |
|
||||
|
||||
## Frame Layout (64 bits after deobfuscation)
|
||||
|
||||
Raw data collects into a 14-byte buffer. First byte is discarded (sync); bytes [1..9] form the 8-byte data frame.
|
||||
|
||||
After XOR deobfuscation:
|
||||
- Bytes 0-3: Serial (32 bits)
|
||||
- Byte 4: Button (8 bits)
|
||||
- Bytes 5-6: Counter (16 bits)
|
||||
- Byte 7: Checksum (additive sum of bytes 0-6)
|
||||
|
||||
## Pair-Based Bit Decoding
|
||||
|
||||
| Pair (te_last, duration) | Action |
|
||||
|--------------------------|--------|
|
||||
| Long + Short | Collect bit(0), bit(1), prev_state=1 |
|
||||
| Short + Long | Collect bit(1), prev_state=0 |
|
||||
| Short + Short | Collect bit(prev_state) |
|
||||
| Long + Long | Collect bit(0), bit(1), prev_state=0 |
|
||||
|
||||
Bit collection uses inverted polarity: state_bit=0 stores a 1.
|
||||
|
||||
## XOR Deobfuscation
|
||||
|
||||
1. Compute parity of data[7] (XOR fold to single bit).
|
||||
2. If parity is odd: XOR bytes [0..6] with data[6] as mask.
|
||||
3. If parity is even: XOR bytes [0..5] with data[5] as mask, also XOR data[6] with data[5].
|
||||
4. Bit interleave swap bytes 5 and 6: swap even/odd bit positions between them.
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** -- Wait for short pulse; save and go to PreambleCheck.
|
||||
2. **PreambleSave** -- Save duration, go to PreambleCheck.
|
||||
3. **PreambleCheck** -- Count short+short pairs; on short+long with count >= 13, start data.
|
||||
4. **DataSave** -- Save duration, go to DataCheck.
|
||||
5. **DataCheck** -- Process pair; if valid continue; if invalid, check completion (80-105 bits + checksum).
|
||||
|
||||
## Buttons
|
||||
|
||||
| Code | Name |
|
||||
|------|--------|
|
||||
| 0x10 | Lock |
|
||||
| 0x20 | Unlock |
|
||||
| 0x40 | Trunk |
|
||||
|
||||
## Encoder
|
||||
|
||||
Not supported (decode-only).
|
||||
|
||||
## Frequencies
|
||||
|
||||
433.92 MHz.
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Mitsubishi V0 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/mitsubishi_v0.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/mitsubishi_v0.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Mitsubishi V0 uses PWM encoding at 250/500 us. 96-bit frame (12 bytes). Level-aware: HIGH pulses are saved, LOW pulses complete the pair. After collection, data is unscrambled via bitwise NOT + counter-derived XOR mask.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-----------|--------|--------------|
|
||||
| Short | 250 us | +/-100 us |
|
||||
| Long | 500 us | +/-100 us |
|
||||
| Min bits | 80 | |
|
||||
| Frame | 96 bits| 12 bytes |
|
||||
|
||||
## PWM Bit Encoding
|
||||
|
||||
| Pair (HIGH, LOW) | Bit |
|
||||
|------------------|-----|
|
||||
| Short HIGH + Long LOW | 1 |
|
||||
| Long HIGH + Short LOW | 0 |
|
||||
|
||||
Bits collected MSB-first into a 12-byte buffer.
|
||||
|
||||
## Frame Layout (96 bits)
|
||||
|
||||
After unscrambling:
|
||||
- Bytes 0-3: Serial (32 bits, big-endian)
|
||||
- Bytes 4-5: Counter (16 bits)
|
||||
- Byte 6: Button (8 bits)
|
||||
- Bytes 7-11: Remaining payload
|
||||
|
||||
## Unscramble Algorithm
|
||||
|
||||
1. Bitwise NOT the first 8 bytes.
|
||||
2. Extract counter from bytes [4..5]: `hi = (counter >> 8) & 0xFF`, `lo = counter & 0xFF`.
|
||||
3. Compute masks: `mask1 = (hi & 0xAA) | (lo & 0x55)`, `mask2 = (lo & 0xAA) | (hi & 0x55)`, `mask3 = mask1 ^ mask2`.
|
||||
4. XOR bytes [0..5] with mask3.
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** -- Wait for HIGH pulse; save duration.
|
||||
2. **DataSave** -- On HIGH: save duration. On LOW (unexpected): reset.
|
||||
3. **DataCheck** -- On LOW: decode pair. If 96 bits collected, unscramble and return. Otherwise continue.
|
||||
|
||||
## Encoder
|
||||
|
||||
Not supported (decode-only).
|
||||
|
||||
## Frequencies
|
||||
|
||||
868.35 MHz.
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Porsche Cayenne Protocol
|
||||
|
||||
**Rust module:** `src/protocols/porsche_cayenne.rs`
|
||||
**Reference:** `Flipper-ARF lib/subghz/protocols/porsche_cayenne.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Porsche keyfobs (internal firmware header name "Porsche AG"). PWM at 1680/3370 µs: SHORT LOW + LONG HIGH
|
||||
= bit 0, LONG LOW + SHORT HIGH = bit 1. 64-bit MSB-first frame, preceded by a 73-pulse 3370 µs sync
|
||||
preamble + a 5930 µs gap pair. The cipher is a 24-bit rotating-register VAG cipher; the counter is
|
||||
recovered by brute-forcing 1..=256 and matching the recomputed encrypted bytes (the C's validity signal).
|
||||
|
||||
> **Note:** Porsche Cayenne shares the wire protocol with KAT's existing **Porsche Touareg** decoder
|
||||
> (the Touareg port is itself a port of this same `porsche_cayenne.c` source). The two are the **same
|
||||
> wire protocol** — identical timing, preamble, 64-bit frame, VAG cipher, and validity check. Cayenne is
|
||||
> registered AFTER Touareg, so Touareg keeps first-match priority and Cayenne never steals a Touareg
|
||||
> capture. Emission is additionally gated on `frame_type` being one of the three values the C emits
|
||||
> (`0b001` Cont, `0b010` First, `0b100` Final) — a strict subset of what Touareg accepts. Cayenne adds an
|
||||
> encoder (the Touareg port is decode-only).
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-------------|---------|------------------------|
|
||||
| Short | 1680 µs | ±500 µs (te_delta) |
|
||||
| Long | 3370 µs | ±500 µs |
|
||||
| Sync | 3370 µs | preamble pulse |
|
||||
| Gap | 5930 µs | preamble→data boundary |
|
||||
| Min bits | 64 | |
|
||||
| Sync min | 15 pairs| (firmware emits 73) |
|
||||
|
||||
## Frame Layout (64 bits / 8 bytes)
|
||||
|
||||
- **byte 0:** `(button << 4) | (frame_type & 0x07)` — button d-pad: Lock=0x01, Unlock=0x02, Trunk=0x04, Open/Panic=0x08; frame_type First=0x02, Cont=0x01, Final=0x04
|
||||
- **bytes 1–3:** serial (24-bit, big-endian)
|
||||
- **bytes 4–7:** encrypted cipher output (24-bit rotating-register VAG cipher seeded from serial, rotated `4 + counter_low` times)
|
||||
|
||||
The `extra` word carries the frame_type; `protocol_display_name` is `Porsche Cayenne [First|Cont|Final]`.
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** PWM (SHORT LOW + LONG HIGH = 0, LONG LOW + SHORT HIGH = 1)
|
||||
- **RF modulation:** AM/OOK
|
||||
- **Encryption:** 24-bit rotating-register VAG cipher; counter recovered by brute force, `counter != 0` is the validity gate
|
||||
- **Frequencies:** 433.92 MHz, 868.35 MHz
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** — wait for a LOW pulse at ~3370 µs.
|
||||
2. **Sync** — count sync pulses (HIGH and LOW at 3370 µs); a 5930 µs gap with ≥15 sync pulses enters GapHigh/GapLow.
|
||||
3. **GapHigh/GapLow** — expect the complementary 5930 µs gap pulse, then enter Data.
|
||||
4. **Data** — decode bit pairs (SHORT LOW + LONG HIGH = 0, LONG LOW + SHORT HIGH = 1); at 64 bits, `parse_data` applies the Cayenne frame_type gate + counter recovery and emits (or stays silent so Touareg owns the frame).
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported (matches `porsche_cayenne_build_upload`). Emits a 4-frame burst (frame types 0b010/0b001/0b100/
|
||||
0b100, cipher counters cnt+1..cnt+4), each frame being 73 sync pairs + 1 gap pair + 64 MSB-first PWM bits.
|
||||
|
||||
## Validation
|
||||
|
||||
Verified by encode→decode round-trips / synthetic-frame checks (no local capture available). Unit tests
|
||||
cover the full encoder path, all three frame types, rejection of undocumented frame types and truncated
|
||||
frames, the counter-recovery limit matching the C, and shared cipher vectors with the Touareg port.
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Porsche Touareg Protocol
|
||||
|
||||
**Rust module:** `src/protocols/porsche_touareg.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/porsche_touareg.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Porsche Touareg uses PWM encoding with very long timing (1680/3370 us). 64-bit frame with a sync preamble (at least 15 sync pulses at 3370 us). Counter is recovered via brute-force using a 24-bit rotation cipher. Originally designed for the Porsche Cayenne.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|------------|----------|--------------|
|
||||
| Short | 1680 us | +/-500 us |
|
||||
| Long | 3370 us | +/-500 us |
|
||||
| Sync | 3370 us | Same as long |
|
||||
| Gap | 5930 us | +/-500 us |
|
||||
| Sync min | 15 pulses| |
|
||||
| Min bits | 64 | |
|
||||
|
||||
## PWM Bit Encoding
|
||||
|
||||
| Pair (LOW, HIGH) | Bit |
|
||||
|------------------|-----|
|
||||
| Short LOW + Long HIGH | 0 |
|
||||
| Long LOW + Short HIGH | 1 |
|
||||
|
||||
## Frame Layout (64 bits = 8 bytes)
|
||||
|
||||
| Byte | Content |
|
||||
|------|---------|
|
||||
| pkt[0] | (button << 4) \| (frame_type & 0x07) |
|
||||
| pkt[1] | serial bits [23:16] |
|
||||
| pkt[2] | serial bits [15:8] |
|
||||
| pkt[3] | serial bits [7:0] |
|
||||
| pkt[4..7] | Encrypted counter/rolling code |
|
||||
|
||||
- **Serial**: 24 bits from pkt[1..3]
|
||||
- **Button**: 4 bits (pkt[0] >> 4)
|
||||
- **Frame type**: 3 bits (pkt[0] & 0x07): 0x02=First, 0x01=Cont, 0x04=Final
|
||||
|
||||
## Counter Recovery (Brute-Force)
|
||||
|
||||
Counter is not in plaintext. The decoder tries counter values 1-256, calling `compute_frame()` for each, and checks if computed bytes [4..7] match received bytes [4..7].
|
||||
|
||||
### Compute Frame Algorithm (24-bit Rotate Cipher)
|
||||
|
||||
1. Initialize 24-bit rotate register from serial bytes: r_h=b3, r_m=b1, r_l=b2.
|
||||
2. ROTATE24: circular left shift across 3 bytes (h<-m, m<-l, l<-h).
|
||||
3. Rotate 4 times + counter_low more times.
|
||||
4. Compute encrypted bytes a9a/a9b/a9c from rotated values XOR'd with inverted counter bits.
|
||||
5. Assemble pkt[4..7] using bitfield packing.
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** -- Wait for LOW pulse matching sync (3370 us).
|
||||
2. **Sync** -- Count sync pulses (both HIGH and LOW). When count >= 15 and gap detected (5930 us), transition to GapHigh or GapLow.
|
||||
3. **GapHigh** -- Expect HIGH gap (5930 us); init data on match.
|
||||
4. **GapLow** -- Expect LOW gap (5930 us); init data on match.
|
||||
5. **Data** -- Decode bit pairs (LOW saved, HIGH completes). At 64 bits, parse data and brute-force counter.
|
||||
|
||||
## Encoder
|
||||
|
||||
Not supported (decode-only).
|
||||
|
||||
## Frequencies
|
||||
|
||||
433.92 MHz, 868.35 MHz.
|
||||
+47
-14
@@ -1,24 +1,33 @@
|
||||
# PSA (Peugeot/Citroën) Protocol
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# PSA (Peugeot/Citroen) Protocol
|
||||
|
||||
**Rust module:** `src/protocols/psa.rs`
|
||||
**Reference:** `REFERENCES/ProtoPirate/protocols/psa.c`
|
||||
|
||||
## Overview
|
||||
|
||||
PSA uses Manchester at 250/500 µs symbol (125/250 µs sub-symbol for preamble). 128 bits total: key1 (64) + validation (16) + key2/rest (48); decode uses key1 + 16-bit validation. TEA decrypt/encrypt with fixed key schedules; mode 0x23 adds an XOR layer. Two modes: seed 0x23 (TEA + XOR), seed 0xF3/0x36 (TEA, BF2 key schedule).
|
||||
PSA uses Manchester encoding with dual preamble pattern support. 128 bits total: key1 (64) + validation (16) + key2/rest (48). Modified TEA (XTEA-like) with dynamic key selection (`key[sum & 3]` and `key[(sum >> 11) & 3]`). Two decode modes: mode 0x23 (direct XOR decrypt with checksum validation) and mode 0x36 (TEA with BF1/BF2 key schedules). Brute-force decryption for mode 0x36 is deferred/partial.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|------------|--------|---------|
|
||||
| Symbol short | 250 µs | ±100 µs |
|
||||
| Symbol long | 500 µs | ±100 µs |
|
||||
| Preamble | 125/250 µs sub-symbols | |
|
||||
|-------------|--------|---------|
|
||||
| Symbol short | 250 us | +/-100 us |
|
||||
| Symbol long | 500 us | +/-100 us |
|
||||
| Preamble P1 | 250 us sub-symbols | Pattern 1 |
|
||||
| Preamble P2 | 125 us sub-symbols | Pattern 2 |
|
||||
| End marker | 1000 us (P1), 500 us (P2) | |
|
||||
| Min bits | 128 | |
|
||||
|
||||
## Encoding
|
||||
## Dual Preamble Patterns
|
||||
|
||||
Manchester; preamble uses 125/250 µs; then 250/500 µs symbols. TEA encrypt; mode 0x23 adds XOR.
|
||||
- **Pattern 1 (250 us):** Preamble uses 250 us sub-symbols; Manchester decode at 250/500 us. Threshold: 70+ transitions.
|
||||
- **Pattern 2 (125 us):** Preamble uses 125 us sub-symbols; Manchester decode at 125/250 us. Threshold: 69+ transitions.
|
||||
|
||||
Pattern type is auto-detected from the first preamble pulse duration.
|
||||
|
||||
## Frame Layout (128 bits)
|
||||
|
||||
@@ -26,18 +35,42 @@ Manchester; preamble uses 125/250 µs; then 250/500 µs symbols. TEA encrypt; mo
|
||||
- validation: 16 bits
|
||||
- key2/rest: 48 bits
|
||||
|
||||
TEA decrypt key1 (and validation); mode 0x23: XOR with BF1 key schedule; mode 0x36: TEA with BF2 key schedule. Serial/button/counter extracted from decrypted key1.
|
||||
## Encryption
|
||||
|
||||
### Modified TEA (XTEA-like)
|
||||
|
||||
Uses dynamic key word selection per round:
|
||||
- Encrypt: `k_idx1 = sum & 3`, then `sum += DELTA`, then `k_idx2 = (sum >> 11) & 3`
|
||||
- Decrypt: `k_idx2 = (sum >> 11) & 3`, then `sum -= DELTA`, then `k_idx1 = sum & 3`
|
||||
- Round function: `(key[k_idx] + sum) ^ (((v >> 5) ^ (v << 4)) + v)`
|
||||
|
||||
### Mode 0x23 (Direct XOR)
|
||||
|
||||
1. Setup byte buffer from key1/key2 (little-endian unpack).
|
||||
2. Calculate checksum over buffer[2..8] (nibble sum * 16).
|
||||
3. Validate: `(checksum ^ key2_high_byte) & 0xF0 == 0`.
|
||||
4. XOR decrypt using `psa_copy_reverse` byte reordering.
|
||||
5. Extract: serial (24-bit), counter (16-bit), button (4-bit), CRC.
|
||||
|
||||
### Mode 0x36 (TEA Brute-Force)
|
||||
|
||||
Direct TEA decrypt attempted with BF1 and BF2 key schedules. Full brute-force (16M iterations per schedule) is available in ProtoPirate but not fully ported to KAT.
|
||||
|
||||
Key schedules:
|
||||
- BF1: `[0x4A434915, 0xD6743C2B, 0x1F29D308, 0xE6B79A64]`
|
||||
- BF2: `[0x4039C240, 0xEDA92CAB, 0x4306C02A, 0x02192A04]`
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **WaitEdge** — Wait for edge/preamble.
|
||||
2. **CountPattern** — Count preamble pattern (125/250 µs).
|
||||
3. **DecodeManchester** — Manchester decode 128 bits; TEA decrypt; apply mode (0x23 XOR or 0x36); extract fields.
|
||||
4. **End** — End marker (e.g. 1000 µs); return decode.
|
||||
1. **WaitEdge (State0)** -- Detect preamble pattern type from first HIGH pulse (250 us -> Pattern 1, 125 us -> Pattern 2).
|
||||
2. **CountPattern250 (State1)** -- Count 250 us preamble pairs; on long pulse with count > 70, transition to Manchester decode.
|
||||
3. **DecodeManchester250 (State2)** -- Manchester decode at 250/500 us. End on 1000 us marker or 121+ bits.
|
||||
4. **CountPattern125 (State3)** -- Count 125 us preamble pairs; on 250 us pulse with count >= 69, transition to Manchester decode.
|
||||
5. **DecodeManchester125 (State4)** -- Manchester decode at 125/250 us. End on 500 us marker or 121+ bits.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported; preamble, Manchester 128 bits, TEA (+ XOR for 0x23).
|
||||
Supported (mode 0x23 only). XOR encrypt, then modified TEA encrypt with BF1 key schedule. Preamble: 70 cycles of 125/125 us + 250/250 us sync. Manchester encoded key1 (64 bits) + validation (16 bits). End marker: 1000 us LOW.
|
||||
|
||||
## Frequencies
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# PSA2 Protocol
|
||||
|
||||
**Rust module:** `src/protocols/psa2.rs`
|
||||
**Reference:** `Flipper-ARF lib/subghz/protocols/psa2.c`
|
||||
|
||||
## Overview
|
||||
|
||||
PSA2 (Peugeot/Citroën — internal name "PSA OLD") is the OLDER PSA variant, distinct from KAT's existing
|
||||
`psa` (modified-TEA/XEA) decoder. Manchester encoding: 250/500 µs symbol (Pattern 1, standard rate) or
|
||||
125/250 µs (Pattern 2, half rate), using the canonical Flipper `manchester_advance` table. 128-bit frame
|
||||
= key1 (64 bits) + key2/validation word: the decoder collects 64 bits → key1, then 16 more (to 80 bits)
|
||||
→ the 16-bit validation field / key2_low.
|
||||
|
||||
Crypto: TEA (Tiny Encryption Algorithm) with a dual brute-force fallback (BF1 0x23000000–0x24000000, BF2
|
||||
0xF3000000–0xF4000000) and a mode23/mode36 selector, validated via a nibble checksum.
|
||||
|
||||
**Performance:** the live decoder only ever runs the cheap O(1) mode23 XOR path
|
||||
(`direct_xor_decrypt`) per frame — it NEVER runs the TEA brute force. The bounded TEA brute force
|
||||
(`decrypt_full`, ~16.7M iters per range) is ported faithfully but is reserved for a deferred-decrypt UI
|
||||
action, mirroring the C exactly. Beyond the C's nibble-checksum gate, KAT adds two false-positive
|
||||
suppressors required by its feed-all-decoders model: a `key2_high` precondition and a valid-button check
|
||||
(PSA2 buttons are Lock=0, Unlock=1, Trunk=2 only). Emission is gated strictly on a successful,
|
||||
field-bearing mode23 decrypt.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|----------------|--------|-----------------------------|
|
||||
| Short (P1) | 250 µs | ±100 µs (te_delta) |
|
||||
| Long (P1) | 500 µs | ±100 µs |
|
||||
| Short (P2) | 125 µs | ±50 µs (half rate) |
|
||||
| Long (P2) | 250 µs | ±50 µs |
|
||||
| End marker | 1000 µs (P1) / 500 µs (P2) | |
|
||||
| Min bits | 128 | key1 (64) + validation (16) collected |
|
||||
| Preamble | >0x46 pairs (P1) / >0x45 (P2) | |
|
||||
|
||||
## Frame Layout (128 bits = key1 64-bit + key2/validation)
|
||||
|
||||
The decoder collects 64 bits → key1, then 16 bits → the validation field (key2_low). After the mode23 XOR
|
||||
decrypt (fields from `extract_fields_mode23`):
|
||||
|
||||
- **serial:** `(buf[2]<<16) | (buf[3]<<8) | buf[4]` (24-bit)
|
||||
- **button:** `buf[8] & 0xF` — Lock=0, Unlock=1, Trunk=2
|
||||
- **counter:** `(buf[5]<<8) | buf[6]` (16-bit)
|
||||
- **crc:** `buf[7]`
|
||||
|
||||
`data` = `(key1_high << 32) | key1_low` (64 bits).
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** Manchester (canonical Flipper table; standard 250/500 µs or half-rate 125/250 µs)
|
||||
- **RF modulation:** AM (OOK)
|
||||
- **Encryption:** TEA + nibble-checksum-gated mode23 XOR path; brute-force fallback reserved for offline decrypt
|
||||
- **Frequencies:** 433.92 MHz
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **WaitEdge (State0)** — detect the preamble rate (250 µs → Pattern 1, 125 µs → Pattern 2).
|
||||
2. **CountPattern250/125 (State1/3)** — count preamble pulses past the threshold, then a long pulse enters the Manchester decode state.
|
||||
3. **DecodeManchester250/125 (State2/4)** — Manchester-decode to 80 bits (key1 + validation), detect end-of-packet, then `finalize_frame` runs the cheap mode23 XOR gate and emits only on a successful field-bearing decrypt.
|
||||
|
||||
## Encoder
|
||||
|
||||
Supported (mode23 path). Increments the counter, builds key1/validation via `encode_mode23` (inverse XOR
|
||||
stage + nibble checksum), then emits an 80-pair preamble, a sync, 64 key1 bits + 16 validation bits
|
||||
MSB-first, and an end burst.
|
||||
|
||||
## Validation
|
||||
|
||||
Decodes REAL IMPORTS captures (IMPORTS/GROUPE PSA, serial `0x99EB25`). Unit tests also cover the TEA
|
||||
round trip, the mode23 encode→decode round trip, the nibble checksum vs. the reference, and the emitted
|
||||
Manchester frame length.
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Scher-Khan Protocol
|
||||
|
||||
**Rust module:** `src/protocols/scher_khan.rs`
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Star Line Protocol
|
||||
|
||||
**Rust module:** `src/protocols/star_line.rs`
|
||||
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary-color: #6366f1;
|
||||
--secondary-color: #8b5cf6;
|
||||
--text-color: #1f2937;
|
||||
--light-bg: #f9fafb;
|
||||
--border-color: #e5e7eb;
|
||||
--success-color: #10b981;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
|
||||
color: var(--text-color);
|
||||
background-color: white;
|
||||
line-height: 1.6;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||
color: white;
|
||||
padding: 40px 0 20px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
header .tagline {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.navbar {
|
||||
background: white;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.navbar ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
padding: 1rem 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.navbar a:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
padding: 60px 0;
|
||||
text-align: center;
|
||||
background: var(--light-bg);
|
||||
}
|
||||
|
||||
.hero h2 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.1rem;
|
||||
color: #6b7280;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.cta-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 28px;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #4f46e5;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--primary-color);
|
||||
border: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--light-bg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Features Section */
|
||||
.features {
|
||||
padding: 60px 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.features h2 {
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
padding: 2rem;
|
||||
background: var(--light-bg);
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--primary-color);
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--primary-color);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* Protocols Section */
|
||||
.protocols {
|
||||
padding: 60px 0;
|
||||
background: var(--light-bg);
|
||||
}
|
||||
|
||||
.protocols h2 {
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.protocol-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.protocol-column h3 {
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.protocol-column ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.protocol-column li {
|
||||
padding: 0.5rem 0;
|
||||
color: #4b5563;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.protocol-column li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Requirements Section */
|
||||
.requirements {
|
||||
padding: 60px 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.requirements h2 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.requirements h3 {
|
||||
color: var(--primary-color);
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.requirements ul {
|
||||
list-style: none;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.requirements li {
|
||||
padding: 0.75rem 0 0.75rem 2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.requirements li:before {
|
||||
content: "✓";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--success-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Documentation Section */
|
||||
.documentation {
|
||||
padding: 60px 0;
|
||||
background: var(--light-bg);
|
||||
}
|
||||
|
||||
.documentation h2 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.documentation > .container > p {
|
||||
margin-bottom: 2rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.doc-links {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.doc-link {
|
||||
padding: 12px 16px;
|
||||
background: white;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.doc-link:hover {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Security Notice */
|
||||
.security-notice {
|
||||
padding: 40px 0;
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
.security-notice h3 {
|
||||
color: #dc2626;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.security-notice p {
|
||||
color: #7f1d1d;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
background: var(--text-color);
|
||||
color: white;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.hero h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.features h2,
|
||||
.protocols h2,
|
||||
.requirements h2,
|
||||
.documentation h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.navbar ul {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.cta-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Subaru Protocol
|
||||
|
||||
**Rust module:** `src/protocols/subaru.rs`
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Suzuki Protocol
|
||||
|
||||
**Rust module:** `src/protocols/suzuki.rs`
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Toyota Protocol
|
||||
|
||||
**Rust module:** `src/protocols/toyota.rs`
|
||||
**Reference:** `Flipper-ARF lib/subghz/protocols/toyota.c`
|
||||
|
||||
## Overview
|
||||
|
||||
Toyota / Lexus KeeLoq keyfobs, a dual-variant decoder. Two variants are detected from the first HIGH
|
||||
pulse width (threshold 310 µs):
|
||||
|
||||
- **Variant A** (Corolla / 433.92 MHz) — PWM pairs: LS (long HIGH + short LOW) = bit 0, SL (short HIGH +
|
||||
long LOW) = bit 1. Preamble = repeated short-short pairs; first non-SS pair is the first data bit.
|
||||
Frame = 68 bits.
|
||||
- **Variant B** (Tundra / 315 MHz) — NRZ: each individual pulse encodes one bit by a midpoint classifier
|
||||
(≤287 µs → 0, >287 µs → 1). Preamble = short-HIGH/long-LOW pairs terminated by a sync gap (LOW between
|
||||
1500 and 2600 µs). Frame = 67 bits.
|
||||
|
||||
KeeLoq hopping: the hop field is left encrypted (the reference does not decrypt or run a CRC). Emission
|
||||
is gated tightly (exact frame bit count + structural preamble/sync + non-zero serial) so it does not
|
||||
false-match the other KeeLoq-PWM protocols. The shared 433 MHz KeeLoq-PWM air encoding means a Variant-A
|
||||
frame that also satisfies Kia V3/V4's 68-bit/CRC4 structure is claimed by Kia V3/V4 first (earlier in the
|
||||
registry); Toyota uniquely claims 60-bit frames and Variant-B NRZ at 315 MHz.
|
||||
|
||||
## Timing
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|------------------|--------|-----------------------------|
|
||||
| A short | 400 µs | ±175 µs (Variant A) |
|
||||
| A long | 800 µs | ±175 µs |
|
||||
| B short | 200 µs | ±120 µs (Variant B preamble)|
|
||||
| B long | 390 µs | ±120 µs |
|
||||
| B NRZ midpoint | 287 µs | ≤ → 0, > → 1 |
|
||||
| B sync gap | 1500–2600 µs | |
|
||||
| Min bits | 60 | A frame = 68, B frame = 67 |
|
||||
| Variant threshold| 310 µs | first HIGH: < B, ≥ A |
|
||||
|
||||
## Frame Layout
|
||||
|
||||
`data = (hop << 32) | (serial << 4) | button` (matches the reference `generic.data`):
|
||||
|
||||
- **hop:** 32-bit KeeLoq ciphertext (left encrypted)
|
||||
- **serial:** 28-bit
|
||||
- **button:** 4-bit
|
||||
|
||||
## RF
|
||||
|
||||
- **Encoding:** Variant A = PWM pairs; Variant B = NRZ
|
||||
- **RF modulation:** AM/OOK
|
||||
- **Encryption:** KeeLoq hop left encrypted (no key / no CRC); a fully-structured frame of the exact bit count with non-zero serial is the validity criterion
|
||||
- **Frequencies:** 433.92 MHz (Variant A), 315 MHz (Variant B)
|
||||
|
||||
## Decoder Steps
|
||||
|
||||
1. **Reset** — detect the variant from the first SHORT HIGH (<310 µs → B, ≥310 µs → A).
|
||||
2. **Variant A** — PreambleA (count SS pairs) → DataA (decode LS/SL pairs, cap at 68 bits, emit on the terminating gap so Kia wins shared frames by registry order).
|
||||
3. **Variant B** — PreambleB (short-HIGH/long-LOW pairs) → on the sync gap → DataB (each pulse is one NRZ bit; emit at 67 bits or on an end gap).
|
||||
|
||||
## Encoder
|
||||
|
||||
Not supported (the reference `encoder` field is NULL). Decode-only.
|
||||
|
||||
## Validation
|
||||
|
||||
Decodes REAL IMPORTS captures (IMPORTS Toyota + Lexus Camry, NRZ Variant B). Unit tests also cover
|
||||
synthetic Variant A and Variant B frames and rejection of an all-zero serial.
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# VAG (VW/Audi/Seat/Skoda) Protocol
|
||||
|
||||
**Rust module:** `src/protocols/vag.rs`
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 813 KiB |
@@ -0,0 +1,94 @@
|
||||
# KAT scripts
|
||||
|
||||
## demod_sub_to_bits.py
|
||||
|
||||
Demodulate Flipper SubGhz RAW `.sub` files into a stream of `1` and `0` bits using duration-based on-off keying (OOK). The script parses level+duration pulses, classifies each as **short** or **long** against configurable timing, then outputs bits according to the chosen encoding.
|
||||
|
||||
### Input format
|
||||
|
||||
The script reads Flipper-style `.sub` files:
|
||||
|
||||
- **RAW_Data:** space-separated signed integers (one per pulse).
|
||||
- **Positive value** = HIGH level, **negative** = LOW; **magnitude** = duration in microseconds.
|
||||
- Optional **Frequency:** line (Hz). If missing, 433 920 000 is assumed.
|
||||
|
||||
Same convention as KAT’s `.sub` import and ProtoPirate’s raw file reader.
|
||||
|
||||
### Encoding modes
|
||||
|
||||
| Mode | Description |
|
||||
|-------------|-------------|
|
||||
| **pwm** | One bit per pulse: short duration → `0`, long duration → `1`. Unmatched durations are skipped. Default. |
|
||||
| **manchester** | Short/long pulses fed into a Manchester state machine (Ford V0–style). Outputs decoded data bits. Long gaps reset state. |
|
||||
| **raw** | No duration decoding: one character per pulse, `1` = HIGH, `0` = LOW. |
|
||||
|
||||
### Timing parameters
|
||||
|
||||
Pulses are classified using nominal short/long durations and a tolerance:
|
||||
|
||||
- **--te-short** — Nominal short duration (µs). Default: 250.
|
||||
- **--te-long** — Nominal long duration (µs). Default: 500.
|
||||
- **--te-delta** — Tolerance (µs): a pulse is “short” if `|duration - te_short| ≤ te_delta`, “long” if `|duration - te_long| ≤ te_delta`. Default: 100.
|
||||
|
||||
Examples:
|
||||
|
||||
- Ford V0–style: `--te-short 250 --te-long 500 --te-delta 100`
|
||||
- VAG/VW 500 µs: `--te-short 500 --te-long 1000 --te-delta 120`
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# PWM decode (short→0, long→1), default 250/500 µs
|
||||
python3 scripts/demod_sub_to_bits.py path/to/file.sub
|
||||
|
||||
# PWM with VAG-like timing
|
||||
python3 scripts/demod_sub_to_bits.py path/to/file.sub --te-short 500 --te-long 1000 --te-delta 120
|
||||
|
||||
# Manchester decode (e.g. Ford)
|
||||
python3 scripts/demod_sub_to_bits.py path/to/file.sub --encoding manchester --te-short 250 --te-long 500
|
||||
|
||||
# Wrap output to 80 characters per line
|
||||
python3 scripts/demod_sub_to_bits.py path/to/file.sub --encoding pwm --wrap 80
|
||||
|
||||
# Only decode HIGH pulses (or --pulse-level low)
|
||||
python3 scripts/demod_sub_to_bits.py path/to/file.sub --encoding pwm --pulse-level high
|
||||
|
||||
# Raw level stream (no duration decoding)
|
||||
python3 scripts/demod_sub_to_bits.py path/to/file.sub --encoding raw
|
||||
|
||||
# Print level:duration for each pulse (no bits)
|
||||
python3 scripts/demod_sub_to_bits.py path/to/file.sub --with-durations
|
||||
```
|
||||
|
||||
### Options summary
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `--encoding` | pwm | `raw`, `pwm`, or `manchester` |
|
||||
| `--te-short` | 250 | Nominal short pulse (µs) |
|
||||
| `--te-long` | 500 | Nominal long pulse (µs) |
|
||||
| `--te-delta` | 100 | Timing tolerance (µs) |
|
||||
| `--pulse-level` | both | For PWM: `high`, `low`, or `both` |
|
||||
| `--gap-us` | 10000 | Manchester: gap (µs) that resets state |
|
||||
| `--wrap` | 0 | Wrap bit string every N characters (0 = no wrap) |
|
||||
| `--with-durations` | off | Print `level:duration` instead of bits |
|
||||
|
||||
### Output
|
||||
|
||||
- Decoded bit string is printed to **stdout** (e.g. `1011001...` or wrapped lines).
|
||||
- A single comment line (pulse count, frequency, encoding, timing) is printed to **stderr**.
|
||||
|
||||
### Requirements
|
||||
|
||||
- Python 3.9+ (for `list[tuple[...]]` type hints; can be relaxed if needed).
|
||||
- No extra dependencies; uses only the standard library.
|
||||
|
||||
### Inspecting raw pulses (e.g. for VAG)
|
||||
|
||||
To see the first pulses of a `.sub` file (level and duration in µs) without decoding:
|
||||
|
||||
```bash
|
||||
python3 scripts/demod_sub_to_bits.py path/to/file.sub --encoding raw --with-durations
|
||||
```
|
||||
|
||||
VAG Type 3/4 expects: first pulse **HIGH** ~500 µs, then LOW ~500 µs, repeated (preamble); after ≥41 such pairs, HIGH ~1000 µs then LOW ~500 µs (sync), then 3×750 µs, then data. If the first pulse is LOW or durations are outside 500±79/80, the decoder will not lock.
|
||||
+505
-57
@@ -1,12 +1,13 @@
|
||||
//! Application state management.
|
||||
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::mpsc::{self, Receiver, Sender};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::capture::{ButtonCommand, Capture};
|
||||
use crate::protocols::ProtocolRegistry;
|
||||
use crate::protocols::{is_keeloq_non_car, ProtocolRegistry};
|
||||
use crate::radio::{HackRfController, LevelDuration, RtlSdrController};
|
||||
use crate::storage::Storage;
|
||||
|
||||
@@ -124,17 +125,22 @@ pub enum InputMode {
|
||||
FobMetaModel,
|
||||
/// Fob export metadata: editing region field
|
||||
FobMetaRegion,
|
||||
/// Fob export metadata: editing command field
|
||||
FobMetaCommand,
|
||||
/// Fob export metadata: editing notes field
|
||||
FobMetaNotes,
|
||||
/// Capture metadata (Year/Make/Model/Region) for vuln lookup — press i on a capture
|
||||
/// Capture metadata (Year/Make/Model/Region/Command) for vuln lookup — press i on a capture
|
||||
CaptureMetaYear,
|
||||
CaptureMetaMake,
|
||||
CaptureMetaModel,
|
||||
CaptureMetaRegion,
|
||||
CaptureMetaCommand,
|
||||
/// License overlay (centered box)
|
||||
License,
|
||||
/// Credits overlay (centered box)
|
||||
Credits,
|
||||
/// :load file browser (import .fob/.sub from import dir)
|
||||
LoadFileBrowser,
|
||||
}
|
||||
|
||||
/// Export format being used
|
||||
@@ -148,6 +154,7 @@ pub enum ExportFormat {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SignalAction {
|
||||
Replay,
|
||||
SendNextCode,
|
||||
Lock,
|
||||
Unlock,
|
||||
Trunk,
|
||||
@@ -158,6 +165,7 @@ pub enum SignalAction {
|
||||
}
|
||||
|
||||
impl SignalAction {
|
||||
/// All actions (car keyfob menu). Barrier/alarm menu is built separately to include SendNextCode only there.
|
||||
pub const ALL: [SignalAction; 8] = [
|
||||
SignalAction::Replay,
|
||||
SignalAction::Lock,
|
||||
@@ -172,6 +180,7 @@ impl SignalAction {
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
SignalAction::Replay => "Replay",
|
||||
SignalAction::SendNextCode => "Send next code",
|
||||
SignalAction::Lock => "TX Lock",
|
||||
SignalAction::Unlock => "TX Unlock",
|
||||
SignalAction::Trunk => "TX Trunk",
|
||||
@@ -211,7 +220,7 @@ impl SettingsField {
|
||||
}
|
||||
|
||||
/// Common keyfob frequencies (Hz)
|
||||
pub const PRESET_FREQUENCIES: [(u32, &str); 9] = [
|
||||
pub const PRESET_FREQUENCIES: [(u32, &str); 10] = [
|
||||
(300_000_000, "300.00 MHz"),
|
||||
(303_875_000, "303.875 MHz"),
|
||||
(310_000_000, "310.00 MHz"),
|
||||
@@ -219,6 +228,7 @@ pub const PRESET_FREQUENCIES: [(u32, &str); 9] = [
|
||||
(318_000_000, "318.00 MHz"),
|
||||
(390_000_000, "390.00 MHz"),
|
||||
(433_920_000, "433.92 MHz"),
|
||||
(434_420_000, "434.42 MHz"),
|
||||
(868_350_000, "868.35 MHz"),
|
||||
(915_000_000, "915.00 MHz"),
|
||||
];
|
||||
@@ -350,16 +360,35 @@ pub struct App {
|
||||
pub fob_meta_model: String,
|
||||
/// Region input buffer (e.g. NA, EU, APAC, etc.)
|
||||
pub fob_meta_region: String,
|
||||
/// Command input buffer (e.g. Unlock, Lock)
|
||||
pub fob_meta_command: String,
|
||||
/// Notes input buffer
|
||||
pub fob_meta_notes: String,
|
||||
|
||||
// -- Capture metadata (Year/Make/Model/Region for vuln lookup, set via 'i') --
|
||||
// -- Capture metadata (Year/Make/Model/Region/Command for vuln lookup, set via 'i') --
|
||||
pub capture_meta_year: String,
|
||||
pub capture_meta_make: String,
|
||||
pub capture_meta_model: String,
|
||||
pub capture_meta_region: String,
|
||||
pub capture_meta_command: String,
|
||||
/// Which capture is being edited (when in CaptureMeta* modes)
|
||||
pub capture_meta_capture_id: Option<u32>,
|
||||
|
||||
// -- Pending transmit (so UI can draw TX state before blocking) --
|
||||
/// Queue of (signal, frequency) to transmit; main loop draws then runs one at a time.
|
||||
pending_transmit_queue: Vec<(Vec<LevelDuration>, u32)>,
|
||||
/// State to restore when queue becomes empty (set when first item is queued).
|
||||
pending_transmit_restore: Option<RadioState>,
|
||||
|
||||
// -- :load file browser --
|
||||
/// Current directory in the load file browser
|
||||
pub load_browser_cwd: PathBuf,
|
||||
/// Selected index in the file list
|
||||
pub load_browser_selected: usize,
|
||||
/// Scroll offset for the file list (so selection stays in view)
|
||||
pub load_browser_scroll: usize,
|
||||
/// Entries: (display name, full path, is_dir)
|
||||
pub load_browser_entries: Vec<(String, PathBuf, bool)>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -463,12 +492,20 @@ impl App {
|
||||
fob_meta_make: String::new(),
|
||||
fob_meta_model: String::new(),
|
||||
fob_meta_region: String::new(),
|
||||
fob_meta_command: String::new(),
|
||||
fob_meta_notes: String::new(),
|
||||
capture_meta_year: String::new(),
|
||||
capture_meta_make: String::new(),
|
||||
capture_meta_model: String::new(),
|
||||
capture_meta_region: String::new(),
|
||||
capture_meta_command: String::new(),
|
||||
capture_meta_capture_id: None,
|
||||
pending_transmit_queue: Vec::new(),
|
||||
pending_transmit_restore: None,
|
||||
load_browser_cwd: PathBuf::new(),
|
||||
load_browser_selected: 0,
|
||||
load_browser_scroll: 0,
|
||||
load_browser_entries: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -558,6 +595,34 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse an ID spec into a list of capture IDs in order.
|
||||
/// Supports: single "1", comma-separated "1, 3, 5", range "1-5", and mixed "1, 3-5, 7".
|
||||
fn parse_id_spec(s: &str) -> Result<Vec<u32>, String> {
|
||||
let mut ids = Vec::new();
|
||||
for part in s.split(',') {
|
||||
let part = part.trim();
|
||||
if part.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some((low, high)) = part.split_once('-') {
|
||||
let low = low.trim().parse::<u32>().map_err(|_| "Invalid ID in range".to_string())?;
|
||||
let high = high.trim().parse::<u32>().map_err(|_| "Invalid ID in range".to_string())?;
|
||||
if low <= high {
|
||||
ids.extend(low..=high);
|
||||
} else {
|
||||
ids.extend((high..=low).rev());
|
||||
}
|
||||
} else {
|
||||
let id = part.parse::<u32>().map_err(|_| "Invalid capture ID".to_string())?;
|
||||
ids.push(id);
|
||||
}
|
||||
}
|
||||
if ids.is_empty() {
|
||||
return Err("No valid IDs".to_string());
|
||||
}
|
||||
Ok(ids)
|
||||
}
|
||||
|
||||
/// Execute a command
|
||||
pub fn execute_command(&mut self, command: &str) -> Result<()> {
|
||||
let parts: Vec<&str> = command.trim().split_whitespace().collect();
|
||||
@@ -587,10 +652,10 @@ impl App {
|
||||
}
|
||||
}
|
||||
}
|
||||
"unlock" => self.transmit_command(parts.get(1), ButtonCommand::Unlock)?,
|
||||
"lock" => self.transmit_command(parts.get(1), ButtonCommand::Lock)?,
|
||||
"trunk" => self.transmit_command(parts.get(1), ButtonCommand::Trunk)?,
|
||||
"panic" => self.transmit_command(parts.get(1), ButtonCommand::Panic)?,
|
||||
"unlock" => self.transmit_command(parts.get(1).map(|_| parts[1..].join(" ")), ButtonCommand::Unlock)?,
|
||||
"lock" => self.transmit_command(parts.get(1).map(|_| parts[1..].join(" ")), ButtonCommand::Lock)?,
|
||||
"trunk" => self.transmit_command(parts.get(1).map(|_| parts[1..].join(" ")), ButtonCommand::Trunk)?,
|
||||
"panic" => self.transmit_command(parts.get(1).map(|_| parts[1..].join(" ")), ButtonCommand::Panic)?,
|
||||
"license" | "licence" => {
|
||||
self.input_mode = InputMode::License;
|
||||
self.overlay_scroll = 0;
|
||||
@@ -599,6 +664,9 @@ impl App {
|
||||
self.input_mode = InputMode::Credits;
|
||||
self.overlay_scroll = 0;
|
||||
}
|
||||
"load" => {
|
||||
self.open_load_browser()?;
|
||||
}
|
||||
"delete" => {
|
||||
if parts.len() < 2 {
|
||||
self.last_error = Some("Usage: :delete <ID> or :delete all".to_string());
|
||||
@@ -610,6 +678,24 @@ impl App {
|
||||
self.delete_capture(parts[1])?;
|
||||
}
|
||||
}
|
||||
"replay" => {
|
||||
let id_spec = parts.get(1).map(|_| parts[1..].join(" "));
|
||||
let id_spec = match id_spec.as_deref() {
|
||||
Some(s) if !s.is_empty() => s,
|
||||
_ => {
|
||||
self.last_error = Some("Usage: :replay <ID> (e.g. 1, 1,3,5, 1-5)".to_string());
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
match Self::parse_id_spec(id_spec) {
|
||||
Ok(ids) => {
|
||||
for id in ids {
|
||||
self.replay_capture(id)?;
|
||||
}
|
||||
}
|
||||
Err(e) => self.last_error = Some(e),
|
||||
}
|
||||
}
|
||||
"lna" => {
|
||||
if parts.len() < 2 {
|
||||
self.last_error = Some("Usage: :lna <0-40>".to_string());
|
||||
@@ -737,8 +823,30 @@ impl App {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transmit a command for a capture
|
||||
fn transmit_command(&mut self, id_str: Option<&&str>, command: ButtonCommand) -> Result<()> {
|
||||
/// Transmit a command for one or more captures. ID spec: "1", "1, 3, 5", "1-5", or mixed.
|
||||
fn transmit_command(&mut self, id_spec: Option<String>, command: ButtonCommand) -> Result<()> {
|
||||
let id_spec = match id_spec.as_deref() {
|
||||
Some(s) if !s.is_empty() => s,
|
||||
_ => {
|
||||
self.last_error = Some(format!("Usage: :{:?} <ID> (e.g. 1, 1,3,5, 1-5)", command).to_lowercase());
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let ids = match Self::parse_id_spec(id_spec) {
|
||||
Ok(ids) => ids,
|
||||
Err(e) => {
|
||||
self.last_error = Some(e);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
for id in ids {
|
||||
self.transmit_one_command(id, command)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transmit a command for a single capture by ID.
|
||||
fn transmit_one_command(&mut self, id: u32, command: ButtonCommand) -> Result<()> {
|
||||
use crate::protocols::DecodedSignal;
|
||||
|
||||
if let Some(ref radio) = self.radio {
|
||||
@@ -751,22 +859,6 @@ impl App {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let id_str = match id_str {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
self.last_error = Some(format!("Usage: :{:?} <ID>", command).to_lowercase());
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let id: u32 = match id_str.parse() {
|
||||
Ok(i) => i,
|
||||
Err(_) => {
|
||||
self.last_error = Some("Invalid capture ID".to_string());
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let capture = match self.captures.iter().find(|c| c.id == id) {
|
||||
Some(c) => c.clone(),
|
||||
None => {
|
||||
@@ -817,11 +909,12 @@ impl App {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ref mut radio) = self.radio {
|
||||
radio.transmit(&signal, capture.frequency)?;
|
||||
self.status_message = Some(format!("Transmitted {:?} for capture {}", command, id));
|
||||
self.pending_transmit_queue.push((signal, capture.frequency));
|
||||
if self.pending_transmit_restore.is_none() {
|
||||
self.pending_transmit_restore = Some(self.radio_state);
|
||||
self.radio_state = RadioState::Transmitting;
|
||||
}
|
||||
|
||||
self.status_message = Some(format!("Transmitted {:?} for capture {}", command, id));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -855,12 +948,107 @@ impl App {
|
||||
.iter()
|
||||
.map(|p| LevelDuration::new(p.level, p.duration_us))
|
||||
.collect();
|
||||
let pair_count = signal.len();
|
||||
|
||||
if let Some(ref mut radio) = self.radio {
|
||||
radio.transmit(&signal, capture.frequency)?;
|
||||
self.status_message = Some(format!("Replayed capture {} ({} pairs)", id, signal.len()));
|
||||
self.pending_transmit_queue.push((signal, capture.frequency));
|
||||
if self.pending_transmit_restore.is_none() {
|
||||
self.pending_transmit_restore = Some(self.radio_state);
|
||||
self.radio_state = RadioState::Transmitting;
|
||||
}
|
||||
self.status_message = Some(format!("Replayed capture {} ({} pairs)", id, pair_count));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transmit the next KeeLoq rolling code for a barrier/alarm capture (same button, counter+1).
|
||||
pub fn transmit_next_code(&mut self, id: u32) -> Result<()> {
|
||||
use crate::protocols::DecodedSignal;
|
||||
|
||||
if let Some(ref radio) = self.radio {
|
||||
if !radio.supports_tx() {
|
||||
self.last_error = Some("Transmit not available – RTL-SDR is receive-only".to_string());
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
self.last_error = Some("No radio device connected".to_string());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let capture = match self.captures.iter().find(|c| c.id == id) {
|
||||
Some(c) => c.clone(),
|
||||
None => {
|
||||
self.last_error = Some(format!("Capture {} not found", id));
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if capture.protocol.is_none() {
|
||||
self.last_error = Some("Cannot transmit: unknown protocol".to_string());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let protocol_name = capture.protocol.as_ref().unwrap();
|
||||
let protocol = match self.protocols.get(protocol_name) {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
self.last_error = Some(format!("Protocol {} not supported for encoding", protocol_name));
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if !protocol.supports_encoding() {
|
||||
self.last_error = Some("Protocol does not support encoding".to_string());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let button = capture.button.unwrap_or(0);
|
||||
let decoded = DecodedSignal {
|
||||
serial: capture.serial,
|
||||
button: capture.button,
|
||||
counter: capture.counter,
|
||||
crc_valid: capture.crc_valid,
|
||||
data: capture.data,
|
||||
data_count_bit: capture.data_count_bit,
|
||||
encoder_capable: true,
|
||||
extra: capture.data_extra,
|
||||
protocol_display_name: None,
|
||||
};
|
||||
|
||||
let signal = match protocol.encode(&decoded, button) {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
self.last_error = Some("Failed to encode next code".to_string());
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
self.pending_transmit_queue.push((signal, capture.frequency));
|
||||
if self.pending_transmit_restore.is_none() {
|
||||
self.pending_transmit_restore = Some(self.radio_state);
|
||||
self.radio_state = RadioState::Transmitting;
|
||||
}
|
||||
self.status_message = Some(format!("Sent next code for capture {} (button {})", id, button));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// True if there are queued transmits (UI should draw then call run_one_pending_transmit).
|
||||
pub fn has_pending_transmit(&self) -> bool {
|
||||
!self.pending_transmit_queue.is_empty()
|
||||
}
|
||||
|
||||
/// Run one queued transmit; restores radio_state when queue is empty. Call after drawing.
|
||||
pub fn run_one_pending_transmit(&mut self) -> Result<()> {
|
||||
let (signal, frequency) = match self.pending_transmit_queue.pop() {
|
||||
Some(p) => p,
|
||||
None => return Ok(()),
|
||||
};
|
||||
if let Some(ref mut radio) = self.radio {
|
||||
radio.transmit(&signal, frequency)?;
|
||||
}
|
||||
if self.pending_transmit_queue.is_empty() {
|
||||
if let Some(prev) = self.pending_transmit_restore.take() {
|
||||
self.radio_state = prev;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1006,12 +1194,21 @@ impl App {
|
||||
|
||||
// -- Signal Action Menu helpers --
|
||||
|
||||
/// Signal actions shown in the menu: all if HackRF (TX), else only export and delete.
|
||||
/// Signal actions shown in the menu. With HackRF: Replay always; Lock/Unlock/Trunk/Panic only when
|
||||
/// the selected capture is encoder-capable and not a barrier/gate/garage or alarm (KeeLoq barrier
|
||||
/// and alarm protocols get Replay + export + delete only). Without TX (e.g. RTL-SDR): only export and delete.
|
||||
pub fn available_signal_actions(&self) -> Vec<SignalAction> {
|
||||
if self.radio.as_ref().map_or(false, |r| r.supports_tx()) {
|
||||
SignalAction::ALL.to_vec()
|
||||
} else {
|
||||
SignalAction::ALL
|
||||
let has_tx = self.radio.as_ref().map_or(false, |r| r.supports_tx());
|
||||
let selected = self
|
||||
.selected_capture
|
||||
.and_then(|idx| self.captures.get(idx));
|
||||
let encoder_capable = selected
|
||||
.map_or(false, |c| c.status == crate::capture::CaptureStatus::EncoderCapable);
|
||||
let is_non_car_keeloq = selected
|
||||
.map_or(false, |c| is_keeloq_non_car(c.protocol_name()));
|
||||
|
||||
if !has_tx {
|
||||
return SignalAction::ALL
|
||||
.iter()
|
||||
.filter(|a| {
|
||||
matches!(
|
||||
@@ -1020,6 +1217,36 @@ impl App {
|
||||
)
|
||||
})
|
||||
.copied()
|
||||
.collect();
|
||||
}
|
||||
|
||||
// Barrier/gate/garage or alarm: Replay, Send next code (encoder next rolling code), export + delete
|
||||
if encoder_capable && is_non_car_keeloq {
|
||||
return vec![
|
||||
SignalAction::Replay,
|
||||
SignalAction::SendNextCode,
|
||||
SignalAction::ExportFob,
|
||||
SignalAction::ExportFlipper,
|
||||
SignalAction::Delete,
|
||||
];
|
||||
}
|
||||
|
||||
if encoder_capable {
|
||||
SignalAction::ALL.to_vec()
|
||||
} else {
|
||||
// Unknown or decoded-only: only Replay + export + delete (no TX Lock/Unlock/Trunk/Panic)
|
||||
SignalAction::ALL
|
||||
.iter()
|
||||
.filter(|a| {
|
||||
!matches!(
|
||||
a,
|
||||
SignalAction::Lock
|
||||
| SignalAction::Unlock
|
||||
| SignalAction::Trunk
|
||||
| SignalAction::Panic
|
||||
)
|
||||
})
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -1043,21 +1270,20 @@ impl App {
|
||||
SignalAction::Replay => {
|
||||
self.replay_capture(capture_id)?;
|
||||
}
|
||||
SignalAction::SendNextCode => {
|
||||
self.transmit_next_code(capture_id)?;
|
||||
}
|
||||
SignalAction::Lock => {
|
||||
let id_str = capture_id.to_string();
|
||||
self.transmit_command(Some(&&*id_str.as_str()), ButtonCommand::Lock)?;
|
||||
self.transmit_command(Some(capture_id.to_string()), ButtonCommand::Lock)?;
|
||||
}
|
||||
SignalAction::Unlock => {
|
||||
let id_str = capture_id.to_string();
|
||||
self.transmit_command(Some(&&*id_str.as_str()), ButtonCommand::Unlock)?;
|
||||
self.transmit_command(Some(capture_id.to_string()), ButtonCommand::Unlock)?;
|
||||
}
|
||||
SignalAction::Trunk => {
|
||||
let id_str = capture_id.to_string();
|
||||
self.transmit_command(Some(&&*id_str.as_str()), ButtonCommand::Trunk)?;
|
||||
self.transmit_command(Some(capture_id.to_string()), ButtonCommand::Trunk)?;
|
||||
}
|
||||
SignalAction::Panic => {
|
||||
let id_str = capture_id.to_string();
|
||||
self.transmit_command(Some(&&*id_str.as_str()), ButtonCommand::Panic)?;
|
||||
self.transmit_command(Some(capture_id.to_string()), ButtonCommand::Panic)?;
|
||||
}
|
||||
SignalAction::ExportFob => {
|
||||
self.export_fob(capture_id)?;
|
||||
@@ -1074,12 +1300,59 @@ impl App {
|
||||
}
|
||||
|
||||
/// Generate a default export filename (without extension) for a capture
|
||||
/// Path relative to the import directory for display; falls back to full path if not under import_dir.
|
||||
fn path_relative_to_import(path: &std::path::Path, import_dir: &std::path::Path) -> String {
|
||||
path.strip_prefix(import_dir)
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|_| path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
/// Default export filename for .fob: Year_Make_Model_Region_Command (same format for all captures).
|
||||
/// Uses capture metadata when set; fallbacks: make from protocol for known, command from button_name(), else "Unknown".
|
||||
fn default_export_filename(capture: &Capture) -> String {
|
||||
format!(
|
||||
"{}_{}",
|
||||
capture.protocol_name().replace(' ', "_").to_lowercase(),
|
||||
capture.serial_hex()
|
||||
)
|
||||
let year = capture
|
||||
.year
|
||||
.as_deref()
|
||||
.unwrap_or("Unknown")
|
||||
.trim()
|
||||
.replace(' ', "_");
|
||||
let make = capture
|
||||
.make
|
||||
.as_deref()
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.map(|s| s.trim().replace(' ', "_"))
|
||||
.unwrap_or_else(|| {
|
||||
if capture.protocol_name().eq_ignore_ascii_case("unknown") {
|
||||
"Unknown".to_string()
|
||||
} else {
|
||||
Self::get_make_for_protocol(capture.protocol_name())
|
||||
.trim()
|
||||
.replace(' ', "_")
|
||||
}
|
||||
});
|
||||
let model = capture
|
||||
.model
|
||||
.as_deref()
|
||||
.unwrap_or("Unknown")
|
||||
.trim()
|
||||
.replace(' ', "_");
|
||||
let region = capture
|
||||
.region
|
||||
.as_deref()
|
||||
.unwrap_or("Unknown")
|
||||
.trim()
|
||||
.replace(' ', "_");
|
||||
let cmd_str = capture
|
||||
.command
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| capture.button_name())
|
||||
.trim();
|
||||
let command = if cmd_str.is_empty() || cmd_str == "-" {
|
||||
"Unknown".to_string()
|
||||
} else {
|
||||
cmd_str.replace(' ', "_")
|
||||
};
|
||||
format!("{}_{}_{}_{}_{}", year, make, model, region, command)
|
||||
}
|
||||
|
||||
/// Start .fob export by entering filename input mode
|
||||
@@ -1089,13 +1362,19 @@ impl App {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Pre-fill filename from protocol + serial
|
||||
let default_name = self.captures.iter().find(|c| c.id == id)
|
||||
// Pre-fill filename: Year_Make_Model_Region_Command_8HEX for all .fob exports
|
||||
let capture = self.captures.iter().find(|c| c.id == id);
|
||||
let default_name = capture
|
||||
.map(|c| Self::default_export_filename(c))
|
||||
.unwrap_or_else(|| format!("capture_{}", id));
|
||||
let suffix_nanos = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos() as u64;
|
||||
let suffix = (suffix_nanos.wrapping_add(id as u64 * 2654435761) % 0x100_000_000) as u32;
|
||||
self.export_filename = format!("{}_{:08X}", default_name, suffix);
|
||||
|
||||
// Pre-fill metadata from capture if set, otherwise make from protocol
|
||||
let capture = self.captures.iter().find(|c| c.id == id);
|
||||
let make = capture
|
||||
.and_then(|c| c.make.as_ref().map(String::clone))
|
||||
.filter(|s| !s.is_empty())
|
||||
@@ -1105,7 +1384,6 @@ impl App {
|
||||
.unwrap_or_default()
|
||||
});
|
||||
self.export_capture_id = Some(id);
|
||||
self.export_filename = default_name;
|
||||
self.export_format = Some(ExportFormat::Fob);
|
||||
self.fob_meta_year = capture
|
||||
.and_then(|c| c.year.as_ref())
|
||||
@@ -1120,6 +1398,16 @@ impl App {
|
||||
.and_then(|c| c.region.as_ref())
|
||||
.map(String::clone)
|
||||
.unwrap_or_default();
|
||||
self.fob_meta_command = capture
|
||||
.and_then(|c| c.command.clone())
|
||||
.unwrap_or_else(|| {
|
||||
let b = capture.map(|c| c.button_name().to_string()).unwrap_or_default();
|
||||
if b.is_empty() || b == "-" {
|
||||
String::new()
|
||||
} else {
|
||||
b
|
||||
}
|
||||
});
|
||||
self.fob_meta_notes = String::new();
|
||||
self.input_mode = InputMode::ExportFilename;
|
||||
Ok(())
|
||||
@@ -1153,10 +1441,32 @@ impl App {
|
||||
make: self.fob_meta_make.clone(),
|
||||
model: self.fob_meta_model.clone(),
|
||||
region: self.fob_meta_region.clone(),
|
||||
command: self.fob_meta_command.clone(),
|
||||
notes: self.fob_meta_notes.clone(),
|
||||
};
|
||||
|
||||
let filename = format!("{}.fob", self.export_filename);
|
||||
// All .fob exports use Year_Make_Model_Region_Command_8HEX; append 8-hex if user removed it
|
||||
let already_has_8hex = self.export_filename.len() >= 9
|
||||
&& self.export_filename.as_bytes()[self.export_filename.len() - 9] == b'_'
|
||||
&& self.export_filename[self.export_filename.len() - 8..]
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_hexdigit());
|
||||
let filename = if already_has_8hex {
|
||||
format!("{}.fob", self.export_filename.trim())
|
||||
} else {
|
||||
let base = self.export_filename.trim();
|
||||
let suffix_nanos = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos() as u64;
|
||||
let suffix = (suffix_nanos.wrapping_add(id as u64 * 2654435761) % 0x100_000_000) as u32;
|
||||
let hex_suffix = format!("{:08X}", suffix);
|
||||
if base.is_empty() {
|
||||
format!("unknown_{}_{}.fob", id, hex_suffix)
|
||||
} else {
|
||||
format!("{}_{}.fob", base, hex_suffix)
|
||||
}
|
||||
};
|
||||
let path = export_dir.join(&filename);
|
||||
|
||||
crate::export::fob::export_fob(
|
||||
@@ -1191,6 +1501,16 @@ impl App {
|
||||
.and_then(|c| c.region.as_ref())
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
self.capture_meta_command = capture
|
||||
.and_then(|c| c.command.clone())
|
||||
.unwrap_or_else(|| {
|
||||
let b = capture.map(|c| c.button_name().to_string()).unwrap_or_default();
|
||||
if b.is_empty() || b == "-" {
|
||||
String::new()
|
||||
} else {
|
||||
b
|
||||
}
|
||||
});
|
||||
self.capture_meta_capture_id = Some(capture_id);
|
||||
self.input_mode = InputMode::CaptureMetaYear;
|
||||
}
|
||||
@@ -1210,6 +1530,7 @@ impl App {
|
||||
capture.make = Some(self.capture_meta_make.clone()).filter(|s| !s.is_empty());
|
||||
capture.model = Some(self.capture_meta_model.clone()).filter(|s| !s.is_empty());
|
||||
capture.region = Some(self.capture_meta_region.clone()).filter(|s| !s.is_empty());
|
||||
capture.command = Some(self.capture_meta_command.clone()).filter(|s| !s.is_empty());
|
||||
}
|
||||
self.input_mode = InputMode::Normal;
|
||||
self.capture_meta_capture_id = None;
|
||||
@@ -1221,6 +1542,104 @@ impl App {
|
||||
self.capture_meta_capture_id = None;
|
||||
}
|
||||
|
||||
/// Open the :load file browser starting at the config import directory.
|
||||
pub fn open_load_browser(&mut self) -> Result<()> {
|
||||
self.load_browser_cwd = self.storage.import_dir().clone();
|
||||
self.load_browser_selected = 0;
|
||||
self.refresh_load_browser_entries()?;
|
||||
self.input_mode = InputMode::LoadFileBrowser;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Refresh the file list for the current load-browser directory.
|
||||
pub fn refresh_load_browser_entries(&mut self) -> Result<()> {
|
||||
let import_dir = self.storage.import_dir().clone();
|
||||
let mut entries: Vec<(String, PathBuf, bool)> = Vec::new();
|
||||
|
||||
if self.load_browser_cwd != import_dir {
|
||||
if let Some(parent) = self.load_browser_cwd.parent() {
|
||||
entries.push(("..".to_string(), parent.to_path_buf(), true));
|
||||
}
|
||||
}
|
||||
|
||||
let dir_entries = match std::fs::read_dir(&self.load_browser_cwd) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
self.last_error = Some(format!("Cannot read directory: {}", e));
|
||||
self.load_browser_entries = entries;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let mut dirs: Vec<(String, PathBuf)> = Vec::new();
|
||||
let mut files: Vec<(String, PathBuf)> = Vec::new();
|
||||
for e in dir_entries.flatten() {
|
||||
let path = e.path();
|
||||
let name = e
|
||||
.file_name()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
if path.is_dir() {
|
||||
dirs.push((name, path));
|
||||
} else if path.is_file() {
|
||||
let ext = path.extension().map(|e| e.to_string_lossy().to_lowercase());
|
||||
if ext.as_deref() == Some("fob") || ext.as_deref() == Some("sub") {
|
||||
files.push((name, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dirs.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
|
||||
files.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
|
||||
|
||||
for (name, path) in dirs {
|
||||
entries.push((name, path, true));
|
||||
}
|
||||
for (name, path) in files {
|
||||
entries.push((name, path, false));
|
||||
}
|
||||
|
||||
let len = entries.len();
|
||||
self.load_browser_entries = entries;
|
||||
self.load_browser_selected = self.load_browser_selected.min(len.saturating_sub(1));
|
||||
const VISIBLE: usize = 16;
|
||||
if self.load_browser_selected < self.load_browser_scroll {
|
||||
self.load_browser_scroll = self.load_browser_selected;
|
||||
}
|
||||
if self.load_browser_selected >= self.load_browser_scroll + VISIBLE {
|
||||
self.load_browser_scroll = self.load_browser_selected.saturating_sub(VISIBLE - 1);
|
||||
}
|
||||
self.load_browser_scroll = self.load_browser_scroll.min(len.saturating_sub(1));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle Enter in the load file browser: open dir or import file.
|
||||
pub fn load_browser_enter(&mut self) -> Result<()> {
|
||||
let Some((_name, path, is_dir)) = self.load_browser_entries.get(self.load_browser_selected)
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let path = path.clone();
|
||||
let is_dir = *is_dir;
|
||||
if is_dir {
|
||||
self.load_browser_cwd = path;
|
||||
self.load_browser_selected = 0;
|
||||
self.refresh_load_browser_entries()?;
|
||||
} else {
|
||||
let name = path.file_name().unwrap_or_default().to_string_lossy().to_string();
|
||||
self.pending_fob_files = vec![path];
|
||||
self.import_fob_files()?;
|
||||
self.input_mode = InputMode::Normal;
|
||||
self.status_message = Some(format!("Imported {}", name));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Close the load file browser without importing.
|
||||
pub fn close_load_browser(&mut self) {
|
||||
self.input_mode = InputMode::Normal;
|
||||
}
|
||||
|
||||
/// Import pending .fob and .sub files into captures list.
|
||||
/// .sub files are decoded with registered protocols after load (no metadata in file).
|
||||
/// When research_mode is off, only decoded captures are added (same as live capture).
|
||||
@@ -1244,6 +1663,7 @@ impl App {
|
||||
// Deduplicate: same signal can decode at multiple stream positions (e.g. Ford V0 across bursts)
|
||||
let mut seen: std::collections::HashSet<(String, u64, Option<u32>, Option<u8>)> =
|
||||
std::collections::HashSet::new();
|
||||
let mut any_decoded_added = false;
|
||||
for (protocol_name, decoded, segment_pairs) in decoded_list {
|
||||
let key = (
|
||||
protocol_name.clone(),
|
||||
@@ -1284,11 +1704,26 @@ impl App {
|
||||
if research_mode || capture.protocol.is_some() {
|
||||
if !self.capture_duplicate_of_existing(&capture) {
|
||||
self.next_capture_id += 1;
|
||||
capture.source_file = Some(Self::path_relative_to_import(path, self.storage.import_dir()));
|
||||
self.captures.push(capture);
|
||||
imported += 1;
|
||||
any_decoded_added = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// When no protocol decoded the stream, add a single Unknown capture if research_mode (same as live capture).
|
||||
if !any_decoded_added && research_mode && !raw_pairs.is_empty() {
|
||||
let mut capture = crate::capture::Capture::from_pairs_with_rf(
|
||||
self.next_capture_id,
|
||||
frequency,
|
||||
raw_pairs.clone(),
|
||||
None,
|
||||
);
|
||||
self.next_capture_id += 1;
|
||||
capture.source_file = Some(Self::path_relative_to_import(path, self.storage.import_dir()));
|
||||
self.captures.push(capture);
|
||||
imported += 1;
|
||||
}
|
||||
}
|
||||
Err(e) => tracing::warn!("Failed to import {:?}: {}", path, e),
|
||||
}
|
||||
@@ -1296,6 +1731,7 @@ impl App {
|
||||
match crate::export::fob::import_fob(path, self.next_capture_id) {
|
||||
Ok(mut capture) => {
|
||||
self.next_capture_id += 1;
|
||||
capture.source_file = Some(Self::path_relative_to_import(path, self.storage.import_dir()));
|
||||
// Re-run decoder when Unknown and raw_pairs present (same as .sub)
|
||||
if capture.status == crate::capture::CaptureStatus::Unknown
|
||||
&& !capture.raw_pairs.is_empty()
|
||||
@@ -1462,13 +1898,23 @@ impl App {
|
||||
match protocol {
|
||||
p if p.starts_with("Kia") => "Kia/Hyundai",
|
||||
p if p.starts_with("Ford") => "Ford",
|
||||
"Honda Static" => "Honda/Acura",
|
||||
"Honda V1" => "Honda/Acura",
|
||||
p if p.starts_with("Fiat") => "Fiat",
|
||||
"Subaru" => "Subaru",
|
||||
"Suzuki" => "Suzuki",
|
||||
"VAG" | "VW" => "VW/Audi/Seat/Skoda",
|
||||
"PSA" => "Peugeot/Citroen",
|
||||
p if p.starts_with("PSA") => "Peugeot/Citroen",
|
||||
"Star Line" => "Star Line",
|
||||
"Scher-Khan" => "Scher-Khan",
|
||||
"Chrysler V0" => "Chrysler/Dodge/Jeep",
|
||||
p if p.starts_with("Land Rover") => "Land Rover",
|
||||
"Toyota" => "Toyota/Lexus",
|
||||
// Covers both "Mazda V0" and "Mazda Siemens".
|
||||
p if p.starts_with("Mazda") => "Mazda",
|
||||
"BMW CAS4" => "BMW",
|
||||
// Covers "Porsche Touareg" and "Porsche Cayenne [First/Cont/Final]".
|
||||
p if p.starts_with("Porsche") => "Porsche",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
@@ -1495,6 +1941,8 @@ impl App {
|
||||
make: None,
|
||||
model: None,
|
||||
region: None,
|
||||
command: None,
|
||||
source_file: None,
|
||||
};
|
||||
self.next_capture_id += 1;
|
||||
self.captures.push(capture);
|
||||
|
||||
+47
-2
@@ -76,6 +76,12 @@ pub struct Capture {
|
||||
/// Region (e.g. NA, EU) for vulnerability lookup and .fob export.
|
||||
#[serde(default)]
|
||||
pub region: Option<String>,
|
||||
/// User-editable command label (e.g. Unlock, Lock) for .fob export and filename; set via 'i' or export form.
|
||||
#[serde(default)]
|
||||
pub command: Option<String>,
|
||||
/// Source file path when imported from .sub or .fob; None for live captures.
|
||||
#[serde(default)]
|
||||
pub source_file: Option<String>,
|
||||
}
|
||||
|
||||
/// Modulation type used by protocol (encoding: PWM vs Manchester)
|
||||
@@ -154,6 +160,8 @@ impl Capture {
|
||||
make: None,
|
||||
model: None,
|
||||
region: None,
|
||||
command: None,
|
||||
source_file: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,19 +225,32 @@ impl Capture {
|
||||
p if p.starts_with("Kia V2") => ModulationType::Manchester,
|
||||
p if p.starts_with("Kia V5") => ModulationType::Manchester,
|
||||
p if p.starts_with("Kia V6") => ModulationType::Manchester,
|
||||
"Ford V0" => ModulationType::Manchester,
|
||||
p if p.starts_with("Kia V7") => ModulationType::Manchester,
|
||||
p if p.starts_with("Ford") => ModulationType::Manchester,
|
||||
"Honda Static" => ModulationType::Manchester,
|
||||
"Mazda Siemens" => ModulationType::Manchester,
|
||||
"BMW CAS4" => ModulationType::Manchester,
|
||||
"Fiat V0" => ModulationType::Manchester,
|
||||
"PSA" => ModulationType::Manchester,
|
||||
"PSA2" => ModulationType::Manchester,
|
||||
"VAG" => ModulationType::Manchester,
|
||||
// Differential Manchester
|
||||
"Land Rover V0" => ModulationType::DifferentialManchester,
|
||||
// PWM-encoded protocols
|
||||
p if p.starts_with("Kia V0") => ModulationType::Pwm,
|
||||
p if p.starts_with("Kia V3") => ModulationType::Pwm,
|
||||
p if p.starts_with("Kia V4") => ModulationType::Pwm,
|
||||
"Honda V1" => ModulationType::Pwm,
|
||||
"Subaru" => ModulationType::Pwm,
|
||||
"Suzuki" => ModulationType::Pwm,
|
||||
"Star Line" => ModulationType::Pwm,
|
||||
p if p.starts_with("Keeloq (") => ModulationType::Pwm,
|
||||
"Scher-Khan" => ModulationType::Pwm,
|
||||
"Chrysler V0" => ModulationType::Pwm,
|
||||
"Land Rover RKE" => ModulationType::Pwm,
|
||||
"Toyota" => ModulationType::Pwm,
|
||||
// Display name is "Porsche Cayenne [First/Cont/Final]"; match the prefix.
|
||||
p if p.starts_with("Porsche Cayenne") => ModulationType::Pwm,
|
||||
// Unknown
|
||||
_ => ModulationType::Unknown,
|
||||
}
|
||||
@@ -244,16 +265,27 @@ impl Capture {
|
||||
p if p.starts_with("Kia V2") => RfModulation::FM,
|
||||
p if p.starts_with("Kia V5") => RfModulation::FM,
|
||||
p if p.starts_with("Kia V6") => RfModulation::FM,
|
||||
p if p.starts_with("Kia V7") => RfModulation::FM,
|
||||
"Scher-Khan" => RfModulation::FM,
|
||||
"PSA" => RfModulation::FM,
|
||||
"PSA2" => RfModulation::AM,
|
||||
"Fiat V0" => RfModulation::FM,
|
||||
"Ford V0" => RfModulation::FM,
|
||||
p if p.starts_with("Ford") => RfModulation::FM,
|
||||
"Honda Static" => RfModulation::FM,
|
||||
"Mazda Siemens" => RfModulation::FM,
|
||||
"Land Rover V0" => RfModulation::FM,
|
||||
// AM only (SubGhzProtocolFlag_AM)
|
||||
p if p.starts_with("Kia V1") => RfModulation::AM,
|
||||
"Honda V1" => RfModulation::AM,
|
||||
"VAG" => RfModulation::AM,
|
||||
"Subaru" => RfModulation::AM,
|
||||
"Suzuki" => RfModulation::AM,
|
||||
"Star Line" => RfModulation::AM,
|
||||
"Chrysler V0" => RfModulation::AM,
|
||||
"Land Rover RKE" => RfModulation::AM,
|
||||
"Toyota" => RfModulation::AM,
|
||||
"BMW CAS4" => RfModulation::AM,
|
||||
p if p.starts_with("Porsche Cayenne") => RfModulation::AM,
|
||||
p if p.starts_with("Keeloq (") => RfModulation::AM,
|
||||
// Both AM and FM (Kia V3/V4)
|
||||
p if p.starts_with("Kia V3") || p.starts_with("Kia V4") => RfModulation::Both,
|
||||
@@ -268,14 +300,27 @@ impl Capture {
|
||||
"Star Line" => "KeeLoq",
|
||||
p if p.starts_with("Keeloq (") => "KeeLoq",
|
||||
"PSA" => "XTEA/XOR",
|
||||
"PSA2" => "TEA",
|
||||
"VAG" => "AUT64/XTEA",
|
||||
"Scher-Khan" => "Magic Code",
|
||||
"Subaru" | "Suzuki" => "Rolling Code",
|
||||
"Chrysler V0" => "Rolling Code",
|
||||
"Land Rover V0" => "Rolling Code",
|
||||
"Land Rover RKE" => "KeeLoq",
|
||||
"Toyota" => "KeeLoq",
|
||||
"Mazda Siemens" => "Siemens XOR",
|
||||
"BMW CAS4" => "CAS4 (rolling)",
|
||||
p if p.starts_with("Porsche Cayenne") => "VAG rolling",
|
||||
// Ford V1 is rolling code; must precede the generic Ford "Fixed Code" arm below.
|
||||
"Ford V1" => "Rolling Code",
|
||||
p if p.starts_with("Ford") => "Fixed Code",
|
||||
"Honda Static" => "Fixed Code",
|
||||
"Honda V1" => "Fixed Code",
|
||||
p if p.starts_with("Fiat") => "Fixed Code",
|
||||
p if p.starts_with("Kia V0") => "Fixed Code",
|
||||
p if p.starts_with("Kia V1") || p.starts_with("Kia V2") => "Fixed Code",
|
||||
p if p.starts_with("Kia V5") || p.starts_with("Kia V6") => "Fixed Code",
|
||||
p if p.starts_with("Kia V7") => "Fixed Code",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
+23
-4
@@ -14,6 +14,8 @@ pub struct FobMetadata {
|
||||
pub make: String,
|
||||
pub model: String,
|
||||
pub region: String,
|
||||
/// Command label (e.g. Unlock, Lock) for export filename and .fob vehicle info.
|
||||
pub command: String,
|
||||
pub notes: String,
|
||||
}
|
||||
|
||||
@@ -61,6 +63,9 @@ pub struct FobVehicleInfo {
|
||||
pub model: Option<String>,
|
||||
#[serde(default)]
|
||||
pub region: Option<String>,
|
||||
/// Command label (e.g. Unlock, Lock); optional for backwards compatibility.
|
||||
#[serde(default)]
|
||||
pub command: Option<String>,
|
||||
#[serde(default)]
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
@@ -119,6 +124,14 @@ pub fn export_fob(
|
||||
Some(m.notes.clone())
|
||||
}
|
||||
});
|
||||
let command = metadata.and_then(|m| {
|
||||
let s = m.command.trim();
|
||||
if s.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(s.to_string())
|
||||
}
|
||||
});
|
||||
|
||||
let raw_pairs = if include_raw && !capture.raw_pairs.is_empty() {
|
||||
Some(
|
||||
@@ -163,6 +176,7 @@ pub fn export_fob(
|
||||
make,
|
||||
model,
|
||||
region,
|
||||
command,
|
||||
notes,
|
||||
},
|
||||
capture: FobCapture {
|
||||
@@ -287,6 +301,7 @@ fn import_fob_v2(fob: &FobFile, next_id: u32) -> Result<Capture> {
|
||||
CaptureStatus::Unknown
|
||||
};
|
||||
|
||||
let vehicle = &fob.vehicle;
|
||||
Ok(Capture {
|
||||
id: next_id,
|
||||
timestamp,
|
||||
@@ -302,10 +317,12 @@ fn import_fob_v2(fob: &FobFile, next_id: u32) -> Result<Capture> {
|
||||
raw_pairs,
|
||||
status,
|
||||
received_rf: None,
|
||||
year: None,
|
||||
make: None,
|
||||
model: None,
|
||||
region: None,
|
||||
year: vehicle.year.map(|y| y.to_string()),
|
||||
make: Some(vehicle.make.clone()).filter(|s| !s.is_empty()),
|
||||
model: vehicle.model.clone().filter(|s| !s.is_empty()),
|
||||
region: vehicle.region.clone().filter(|s| !s.is_empty()),
|
||||
command: vehicle.command.clone().filter(|s| !s.is_empty()),
|
||||
source_file: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -376,6 +393,8 @@ fn import_fob_v1(fob: &FobFileV1, next_id: u32) -> Result<Capture> {
|
||||
make: None,
|
||||
model: None,
|
||||
region: None,
|
||||
command: None,
|
||||
source_file: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+75
-4
@@ -176,6 +176,10 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
|
||||
if app.input_mode == InputMode::Command {
|
||||
app.input_mode = InputMode::Normal;
|
||||
}
|
||||
while app.has_pending_transmit() {
|
||||
terminal.draw(|f| draw_ui(f, app))?;
|
||||
app.run_one_pending_transmit()?;
|
||||
}
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
app.command_input.push(c);
|
||||
@@ -209,6 +213,10 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
|
||||
if app.input_mode == InputMode::SignalMenu {
|
||||
app.input_mode = InputMode::Normal;
|
||||
}
|
||||
while app.has_pending_transmit() {
|
||||
terminal.draw(|f| draw_ui(f, app))?;
|
||||
app.run_one_pending_transmit()?;
|
||||
}
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
app.input_mode = InputMode::Normal;
|
||||
@@ -380,10 +388,10 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
|
||||
_ => {}
|
||||
},
|
||||
|
||||
// .fob export metadata: Region -> Notes
|
||||
// .fob export metadata: Region -> Command
|
||||
InputMode::FobMetaRegion => match key.code {
|
||||
KeyCode::Enter => {
|
||||
app.input_mode = InputMode::FobMetaNotes;
|
||||
app.input_mode = InputMode::FobMetaCommand;
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
app.fob_meta_region.push(c);
|
||||
@@ -398,6 +406,24 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
|
||||
_ => {}
|
||||
},
|
||||
|
||||
// .fob export metadata: Command -> Notes
|
||||
InputMode::FobMetaCommand => match key.code {
|
||||
KeyCode::Enter => {
|
||||
app.input_mode = InputMode::FobMetaNotes;
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
app.fob_meta_command.push(c);
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
app.fob_meta_command.pop();
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
app.export_capture_id = None;
|
||||
app.input_mode = InputMode::Normal;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
// .fob export metadata: Notes -> Export
|
||||
InputMode::FobMetaNotes => match key.code {
|
||||
KeyCode::Enter => {
|
||||
@@ -417,7 +443,7 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
|
||||
_ => {}
|
||||
},
|
||||
|
||||
// Capture metadata (Year/Make/Model/Region for vuln lookup)
|
||||
// Capture metadata (Year/Make/Model/Region/Command for vuln lookup)
|
||||
InputMode::CaptureMetaYear => match key.code {
|
||||
KeyCode::Enter => {
|
||||
app.input_mode = InputMode::CaptureMetaMake;
|
||||
@@ -467,7 +493,7 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
|
||||
},
|
||||
InputMode::CaptureMetaRegion => match key.code {
|
||||
KeyCode::Enter => {
|
||||
app.save_capture_meta();
|
||||
app.input_mode = InputMode::CaptureMetaCommand;
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
app.capture_meta_region.push(c);
|
||||
@@ -480,6 +506,51 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
InputMode::CaptureMetaCommand => match key.code {
|
||||
KeyCode::Enter => {
|
||||
app.save_capture_meta();
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
app.capture_meta_command.push(c);
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
app.capture_meta_command.pop();
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
app.cancel_capture_meta();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
InputMode::LoadFileBrowser => {
|
||||
const VISIBLE: usize = 16;
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
app.close_load_browser();
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
let _ = app.load_browser_enter();
|
||||
}
|
||||
KeyCode::Up | KeyCode::Char('k') => {
|
||||
if app.load_browser_selected > 0 {
|
||||
app.load_browser_selected -= 1;
|
||||
if app.load_browser_selected < app.load_browser_scroll {
|
||||
app.load_browser_scroll = app.load_browser_selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Down | KeyCode::Char('j') => {
|
||||
let max = app.load_browser_entries.len().saturating_sub(1);
|
||||
if app.load_browser_selected < max {
|
||||
app.load_browser_selected += 1;
|
||||
if app.load_browser_selected >= app.load_browser_scroll + VISIBLE {
|
||||
app.load_browser_scroll = app.load_browser_selected - VISIBLE + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
|
||||
InputMode::License | InputMode::Credits => match key.code {
|
||||
KeyCode::Esc | KeyCode::Enter => {
|
||||
|
||||
@@ -0,0 +1,461 @@
|
||||
//! BMW CAS4 protocol decoder
|
||||
//!
|
||||
//! Aligned with Flipper-ARF reference: `lib/subghz/protocols/bmw_cas4.c` and `bmw_cas4.h`.
|
||||
//! Manchester 500/1000µs (te_delta 150), 64 bits (8 bytes), AM/OOK. The CAS4 rolling cipher's
|
||||
//! manufacturer key is not available, so the encrypted portion is left as-is — the frame is only
|
||||
//! framed and validated, not decrypted. Emission is gated on two fixed marker bytes
|
||||
//! (byte[0]==0x30 && byte[6]==0xC5), which makes the protocol specific and prevents false matches.
|
||||
//! Decode-only: the reference encoder is a non-functional stub (`yield` returns reset,
|
||||
//! `deserialize` returns error), so `supports_encoding()` is false and `encode()` is None.
|
||||
//!
|
||||
//! Decoder steps: Reset → Preamble (≥10 pulses of 300-700µs) → Data. The preamble→data transition
|
||||
//! is triggered by a long low gap (≥1800µs). Manchester polarity is `level ? Low : High` (Flipper
|
||||
//! manchester_decoder.h event order: 0=ShortLow, 1=ShortHigh, 2=LongLow, 3=LongHigh), the same
|
||||
//! mapping as Ford V0 / common.
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 500;
|
||||
const TE_LONG: u32 = 1000;
|
||||
const TE_DELTA: u32 = 150;
|
||||
const DATA_BITS: usize = 64;
|
||||
const DATA_BYTES: usize = 8;
|
||||
|
||||
// Preamble pulse window and minimum count (BMW_CAS4_PREAMBLE_PULSE_MIN/MAX, BMW_CAS4_PREAMBLE_MIN).
|
||||
const PREAMBLE_PULSE_MIN: u32 = 300;
|
||||
const PREAMBLE_PULSE_MAX: u32 = 700;
|
||||
const PREAMBLE_MIN: u16 = 10;
|
||||
// Long low gap that separates the preamble from the data burst (BMW_CAS4_GAP_MIN).
|
||||
const GAP_MIN: u32 = 1800;
|
||||
|
||||
// Fixed validation markers (BMW_CAS4_BYTE0_MARKER, BMW_CAS4_BYTE6_MARKER).
|
||||
const BYTE0_MARKER: u8 = 0x30;
|
||||
const BYTE6_MARKER: u8 = 0xC5;
|
||||
|
||||
/// Manchester state machine (Flipper manchester_decoder.h transition table).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum ManchesterState {
|
||||
Mid0 = 0,
|
||||
Mid1 = 1,
|
||||
Start0 = 2,
|
||||
Start1 = 3,
|
||||
}
|
||||
|
||||
/// Decoder step states (matches BmwCas4DecoderStep in bmw_cas4.c).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Preamble,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// BMW CAS4 protocol decoder (matches SubGhzProtocolDecoderBmwCas4).
|
||||
pub struct BmwCas4Decoder {
|
||||
step: DecoderStep,
|
||||
manchester_state: ManchesterState,
|
||||
preamble_count: u16,
|
||||
raw_data: [u8; DATA_BYTES],
|
||||
bit_count: usize,
|
||||
decode_data: u64,
|
||||
}
|
||||
|
||||
impl BmwCas4Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
preamble_count: 0,
|
||||
raw_data: [0; DATA_BYTES],
|
||||
bit_count: 0,
|
||||
decode_data: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset accumulators (matches subghz_protocol_decoder_bmw_cas4_reset).
|
||||
fn reset_state(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
self.preamble_count = 0;
|
||||
self.raw_data = [0; DATA_BYTES];
|
||||
self.bit_count = 0;
|
||||
self.decode_data = 0;
|
||||
}
|
||||
|
||||
fn is_short(d: u32) -> bool {
|
||||
duration_diff!(d, TE_SHORT) < TE_DELTA
|
||||
}
|
||||
|
||||
fn is_long(d: u32) -> bool {
|
||||
duration_diff!(d, TE_LONG) < TE_DELTA
|
||||
}
|
||||
|
||||
/// True when the duration is within the preamble pulse window (300-700µs).
|
||||
fn is_preamble_pulse(d: u32) -> bool {
|
||||
d >= PREAMBLE_PULSE_MIN && d <= PREAMBLE_PULSE_MAX
|
||||
}
|
||||
|
||||
/// Flipper Manchester transition table. Event 0=ShortLow, 1=ShortHigh, 2=LongLow, 3=LongHigh.
|
||||
/// Returns Some(bit) when a bit emits.
|
||||
fn manchester_advance(&mut self, event: u8) -> Option<bool> {
|
||||
let (new_state, emit) = match (self.manchester_state, event) {
|
||||
(ManchesterState::Mid0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 1) => (ManchesterState::Start1, true),
|
||||
(ManchesterState::Mid0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 3) => (ManchesterState::Mid1, true),
|
||||
|
||||
(ManchesterState::Mid1, 0) => (ManchesterState::Start0, true),
|
||||
(ManchesterState::Mid1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Mid1, 2) => (ManchesterState::Mid0, true),
|
||||
(ManchesterState::Mid1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 1) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start1, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Start1, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
_ => (ManchesterState::Mid1, false),
|
||||
};
|
||||
self.manchester_state = new_state;
|
||||
if emit { Some((event & 1) == 1) } else { None }
|
||||
}
|
||||
|
||||
/// Map (level, duration) → Manchester event. BMW CAS4 polarity: level ? Low : High
|
||||
/// (matches the C `event = level ? ManchesterEventShortLow : ManchesterEventShortHigh`).
|
||||
fn pulse_event(level: bool, duration: u32) -> Option<u8> {
|
||||
if Self::is_short(duration) {
|
||||
Some(if level { 0 } else { 1 })
|
||||
} else if Self::is_long(duration) {
|
||||
Some(if level { 2 } else { 3 })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a decoded bit MSB-first into raw_data and the 64-bit accumulator
|
||||
/// (matches the C bit-packing in the Data step).
|
||||
fn add_bit(&mut self, bit: bool) {
|
||||
if self.bit_count < DATA_BITS {
|
||||
let byte_idx = self.bit_count / 8;
|
||||
let bit_pos = 7 - (self.bit_count % 8);
|
||||
if bit {
|
||||
self.raw_data[byte_idx] |= 1 << bit_pos;
|
||||
}
|
||||
self.decode_data = (self.decode_data << 1) | (bit as u64);
|
||||
}
|
||||
self.bit_count += 1;
|
||||
}
|
||||
|
||||
/// Validate the fixed markers and build the decoded signal.
|
||||
/// Fields: serial = bytes[1..4] (24-bit), button = byte[7], counter = byte[5].
|
||||
/// The CAS4 rolling cipher is left undecrypted; the markers serve as the integrity gate.
|
||||
fn build_signal(&self) -> DecodedSignal {
|
||||
let b = &self.raw_data;
|
||||
let serial = ((b[1] as u32) << 16) | ((b[2] as u32) << 8) | (b[3] as u32);
|
||||
let counter = b[5] as u16;
|
||||
let button = b[7];
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid: true, // fixed markers (byte[0]==0x30 && byte[6]==0xC5) validated the frame
|
||||
data: self.decode_data,
|
||||
data_count_bit: DATA_BITS,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for BmwCas4Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"BMW CAS4"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: DATA_BITS,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
// SubGhzProtocolFlag_433 only (no 315 listed in the C).
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.reset_state();
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
// Begin on a high preamble pulse within the 300-700µs window.
|
||||
DecoderStep::Reset => {
|
||||
if level && Self::is_preamble_pulse(duration) {
|
||||
self.step = DecoderStep::Preamble;
|
||||
self.preamble_count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Preamble => {
|
||||
if Self::is_preamble_pulse(duration) {
|
||||
self.preamble_count += 1;
|
||||
} else if !level && duration >= GAP_MIN {
|
||||
if self.preamble_count >= PREAMBLE_MIN {
|
||||
// Enter data: clear accumulators and reset the Manchester state.
|
||||
self.bit_count = 0;
|
||||
self.decode_data = 0;
|
||||
self.raw_data = [0; DATA_BYTES];
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
self.step = DecoderStep::Data;
|
||||
} else {
|
||||
self.reset_state();
|
||||
}
|
||||
} else {
|
||||
self.reset_state();
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Data => {
|
||||
if self.bit_count >= DATA_BITS {
|
||||
self.reset_state();
|
||||
return None;
|
||||
}
|
||||
|
||||
let event = Self::pulse_event(level, duration);
|
||||
if let Some(ev) = event {
|
||||
if let Some(bit) = self.manchester_advance(ev) {
|
||||
self.add_bit(bit);
|
||||
|
||||
if self.bit_count == DATA_BITS {
|
||||
let valid = self.raw_data[0] == BYTE0_MARKER
|
||||
&& self.raw_data[6] == BYTE6_MARKER;
|
||||
let result = if valid { Some(self.build_signal()) } else { None };
|
||||
self.reset_state();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Out-of-range pulse aborts the frame (matches the C ManchesterEventReset path).
|
||||
self.reset_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BmwCas4Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Map a Manchester event index back to its (level, duration) pulse, using the decoder's
|
||||
/// polarity (level ? Low : High): 0=ShortLow(level,500), 1=ShortHigh(!level,500),
|
||||
/// 2=LongLow(level,1000), 3=LongHigh(!level,1000).
|
||||
fn event_pulse(event: u8) -> (bool, u32) {
|
||||
match event {
|
||||
0 => (true, TE_SHORT),
|
||||
1 => (false, TE_SHORT),
|
||||
2 => (true, TE_LONG),
|
||||
3 => (false, TE_LONG),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Pure transition table (same as `manchester_advance`) for the test encoder. Returns
|
||||
/// (next_state, Some(bit)) when a bit emits.
|
||||
fn advance(state: ManchesterState, event: u8) -> (ManchesterState, Option<bool>) {
|
||||
use ManchesterState::{Mid0, Mid1, Start0, Start1};
|
||||
let (ns, emit) = match (state, event) {
|
||||
(Mid0, 0) => (Mid0, false),
|
||||
(Mid0, 1) => (Start1, true),
|
||||
(Mid0, 2) => (Mid0, false),
|
||||
(Mid0, 3) => (Mid1, true),
|
||||
(Mid1, 0) => (Start0, true),
|
||||
(Mid1, 1) => (Mid1, false),
|
||||
(Mid1, 2) => (Mid0, true),
|
||||
(Mid1, 3) => (Mid1, false),
|
||||
(Start0, 0) => (Mid0, false),
|
||||
(Start0, 1) => (Mid0, false),
|
||||
(Start0, 2) => (Mid0, false),
|
||||
(Start0, 3) => (Mid1, false),
|
||||
(Start1, 0) => (Mid0, false),
|
||||
(Start1, 1) => (Mid1, false),
|
||||
(Start1, 2) => (Mid0, false),
|
||||
(Start1, 3) => (Mid1, false),
|
||||
_ => (Mid1, false),
|
||||
};
|
||||
(ns, if emit { Some((event & 1) == 1) } else { None })
|
||||
}
|
||||
|
||||
/// Faithfully Manchester-encode `bits` into level/duration pulses by *driving the decoder's
|
||||
/// own transition table*. This guarantees the produced waveform is one the decoder accepts —
|
||||
/// it does not assume any closed-form biphase rule (the Flipper table is differential, so a
|
||||
/// fixed per-bit pattern does not exist). Backtracking DFS over the tiny state space (with a
|
||||
/// visited set on (state, last_level, bit_index) to break no-emit cycles) finds a pulse stream
|
||||
/// whose decoded bits equal `bits`, respecting physical level alternation between pulses.
|
||||
///
|
||||
/// Note: the decoder starts at Mid1, which can only emit a `0` first — so the first bit must be
|
||||
/// 0 (true for any BMW CAS4 frame: byte[0]==0x30 begins with bit 0).
|
||||
fn manchester_encode_bits(bits: &[bool]) -> Option<Vec<(bool, u32)>> {
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn dfs(
|
||||
state: ManchesterState,
|
||||
last_level: Option<bool>,
|
||||
i: usize,
|
||||
bits: &[bool],
|
||||
acc: &mut Vec<u8>,
|
||||
seen: &mut std::collections::HashSet<(ManchesterState, Option<bool>, usize)>,
|
||||
) -> bool {
|
||||
if i == bits.len() {
|
||||
return true;
|
||||
}
|
||||
let key = (state, last_level, i);
|
||||
if seen.contains(&key) {
|
||||
return false;
|
||||
}
|
||||
seen.insert(key);
|
||||
for event in 0u8..4 {
|
||||
let (level, _dur) = event_pulse(event);
|
||||
if last_level == Some(level) {
|
||||
continue; // physical OOK stream must alternate level between pulses
|
||||
}
|
||||
let (ns, bit) = advance(state, event);
|
||||
match bit {
|
||||
None => {
|
||||
acc.push(event);
|
||||
if dfs(ns, Some(level), i, bits, acc, seen) {
|
||||
return true;
|
||||
}
|
||||
acc.pop();
|
||||
}
|
||||
Some(b) if b == bits[i] => {
|
||||
acc.push(event);
|
||||
if dfs(ns, Some(level), i + 1, bits, acc, seen) {
|
||||
return true;
|
||||
}
|
||||
acc.pop();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
let mut events: Vec<u8> = Vec::new();
|
||||
let mut seen: HashSet<(ManchesterState, Option<bool>, usize)> = HashSet::new();
|
||||
if !dfs(ManchesterState::Mid1, None, 0, bits, &mut events, &mut seen) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Translate events → pulses, merging adjacent equal levels (a repeated level is a long).
|
||||
let mut merged: Vec<(bool, u32)> = Vec::new();
|
||||
for ev in events {
|
||||
let (lvl, dur) = event_pulse(ev);
|
||||
if let Some(last) = merged.last_mut() {
|
||||
if last.0 == lvl {
|
||||
last.1 += dur;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
merged.push((lvl, dur));
|
||||
}
|
||||
Some(merged)
|
||||
}
|
||||
|
||||
/// Build a full BMW CAS4 frame: preamble pulses, long low gap, then Manchester data.
|
||||
fn build_frame(bytes: &[u8; DATA_BYTES]) -> Vec<(bool, u32)> {
|
||||
let mut pairs: Vec<(bool, u32)> = Vec::new();
|
||||
// Preamble: 12 high pulses of ~500µs separated by short lows (within the 300-700 window).
|
||||
for _ in 0..12 {
|
||||
pairs.push((true, 500));
|
||||
pairs.push((false, 500));
|
||||
}
|
||||
// Long low gap (≥1800µs) ending the preamble. Overwrite the last low with the gap.
|
||||
if let Some(last) = pairs.last_mut() {
|
||||
if !last.0 {
|
||||
last.1 = GAP_MIN + 200;
|
||||
}
|
||||
}
|
||||
// Manchester-encoded data bits, MSB-first.
|
||||
let mut bits: Vec<bool> = Vec::with_capacity(DATA_BITS);
|
||||
for &byte in bytes.iter() {
|
||||
for i in (0..8).rev() {
|
||||
bits.push((byte >> i) & 1 != 0);
|
||||
}
|
||||
}
|
||||
let data = manchester_encode_bits(&bits).expect("test frame must be encodable (first bit 0)");
|
||||
pairs.extend(data);
|
||||
// Trailing gap to flush.
|
||||
pairs.push((false, GAP_MIN + 500));
|
||||
pairs
|
||||
}
|
||||
|
||||
/// Feed a pair stream through a fresh decoder and return the first decode (trying both
|
||||
/// polarities, matching the registry behaviour).
|
||||
fn decode_pairs(pairs: &[(bool, u32)]) -> Option<DecodedSignal> {
|
||||
for invert in [false, true] {
|
||||
let mut dec = BmwCas4Decoder::new();
|
||||
for &(lvl, dur) in pairs {
|
||||
let level = if invert { !lvl } else { lvl };
|
||||
if let Some(sig) = dec.feed(level, dur) {
|
||||
return Some(sig);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_synthetic_frame_with_markers() {
|
||||
// byte[0]=0x30 and byte[6]=0xC5 are the required markers; the rest is the (encrypted)
|
||||
// payload. serial = bytes[1..4], counter = byte[5], button = byte[7].
|
||||
let frame: [u8; DATA_BYTES] = [0x30, 0x12, 0x34, 0x56, 0xAB, 0x07, 0xC5, 0x02];
|
||||
let pairs = build_frame(&frame);
|
||||
let sig = decode_pairs(&pairs).expect("synthetic BMW CAS4 frame should decode");
|
||||
|
||||
assert!(sig.crc_valid, "fixed markers should set crc_valid=true");
|
||||
assert_eq!(sig.data_count_bit, DATA_BITS);
|
||||
assert_eq!(sig.data, u64::from_be_bytes(frame), "data must equal the 64-bit frame");
|
||||
assert_eq!(sig.serial, Some(0x123456), "serial = bytes[1..4]");
|
||||
assert_eq!(sig.counter, Some(0x07), "counter = byte[5]");
|
||||
assert_eq!(sig.button, Some(0x02), "button = byte[7]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_frame_with_wrong_markers() {
|
||||
// Same structure but byte[0] and byte[6] are NOT the markers → must not decode.
|
||||
let frame: [u8; DATA_BYTES] = [0x31, 0x12, 0x34, 0x56, 0xAB, 0x07, 0xC4, 0x02];
|
||||
let pairs = build_frame(&frame);
|
||||
assert!(
|
||||
decode_pairs(&pairs).is_none(),
|
||||
"a frame with wrong marker bytes must be rejected"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
//! Chrysler V0 protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/chrysler_v0.c` and
|
||||
//! `chrysler_v0.h`. Used by Chrysler/Dodge/Jeep keyfobs.
|
||||
//!
|
||||
//! Protocol: PWM with a short HIGH pulse and TWO long-LOW symbols. A "1" payload bit is
|
||||
//! HIGH≈600µs (te_one_short) + LOW≈3400µs (te_long_a); a "0" payload bit is HIGH≈300µs
|
||||
//! (te_short) + LOW≈3700µs (te_long_b). te_delta≈150, long_delta≈400, te_gap≈8000,
|
||||
//! frame_gap≈15600. ~24 preamble pairs (short HIGH + long_b LOW) precede each frame.
|
||||
//!
|
||||
//! Frame: 80 bits. The first 64 bits are `decode_data` (payload bytes 0..7), the last 16 bits
|
||||
//! are `data_2` (payload bytes 8,9). The 80-bit frame exceeds u64, so [DecodedSignal::data]
|
||||
//! reports the most-significant 64 bits and `data_count_bit = 80` (matches psa.rs/kia_v6.rs).
|
||||
//!
|
||||
//! Crypto (proprietary seed-XOR, ported exactly from chrysler_v0.c `decode`):
|
||||
//! - `seed = reverse6(key[0] >> 2)` — a 6-bit reversed counter used as the transform key.
|
||||
//! - `transform_block`: XOR all 9 transformed bytes with `xor_table[seed & 0x0F]`, with an extra
|
||||
//! nibble flip when the (Lock) button is set.
|
||||
//! - Dual payload A (seed even, carries serial+counter) / B (seed odd, carries serial). The frame
|
||||
//! is gated on a structural `check_ok` (matches the C), so it does not false-match other
|
||||
//! protocols. `crc_valid` reflects that `check_ok`.
|
||||
//!
|
||||
//! RF: AM, 315 + 433.92 MHz. Encoder present (ENABLE_EMULATE_FEATURE): builds preamble + dual
|
||||
//! 80-bit PWM frames with frame gaps.
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 0x12C; // 300
|
||||
const TE_DELTA: u32 = 0x96; // 150
|
||||
const TE_LONG_A: u32 = 0xD48; // 3400
|
||||
const TE_LONG_B: u32 = 0xE74; // 3700
|
||||
const TE_LONG_DELTA: u32 = 0x190; // 400
|
||||
const TE_GAP: u32 = 0x1F40; // 8000
|
||||
const TE_ONE_SHORT: u32 = 0x258; // 600
|
||||
const FRAME_GAP: u32 = 0x3CF0; // 15600
|
||||
const PREAMBLE_PAIRS: usize = 24;
|
||||
const DECODE_BIT_COUNT: usize = 0x50; // 80
|
||||
|
||||
/// XOR table (chrysler_v0_xor_table) — indexed by `seed & 0x0F`.
|
||||
const XOR_TABLE: [u8; 16] = [
|
||||
0x0F, 0x02, 0x40, 0x0C, 0x30, 0x0E, 0x70, 0x08, 0x10, 0x0A, 0x50, 0xF4, 0x2F, 0xF6, 0x6F, 0xF0,
|
||||
];
|
||||
|
||||
/// Decoder steps (matches Chrysler_V0DecoderStep)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Seek,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// Chrysler V0 protocol decoder
|
||||
pub struct ChryslerV0Decoder {
|
||||
step: DecoderStep,
|
||||
packet_bit_count: u16,
|
||||
te_last: u32,
|
||||
decode_data: u64,
|
||||
decode_count_bit: u8,
|
||||
data_2: u16,
|
||||
}
|
||||
|
||||
/// Result of `decode_packet`: structural fields extracted from a candidate frame.
|
||||
struct Decoded {
|
||||
check_ok: bool,
|
||||
button: u8,
|
||||
/// Transform seed (reversed 6-bit counter); retained for diagnostics.
|
||||
#[allow(dead_code)]
|
||||
seed: u8,
|
||||
/// Serial: SnA (counter-frame) or SnB (serial-frame).
|
||||
serial: u32,
|
||||
/// Rolling counter (only meaningful for the A/even frame).
|
||||
counter: u32,
|
||||
}
|
||||
|
||||
impl ChryslerV0Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
packet_bit_count: 0,
|
||||
te_last: 0,
|
||||
decode_data: 0,
|
||||
decode_count_bit: 0,
|
||||
data_2: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_short(d: u32) -> bool {
|
||||
duration_diff!(d, TE_SHORT) <= TE_DELTA
|
||||
}
|
||||
|
||||
fn is_long_mark(d: u32) -> bool {
|
||||
duration_diff!(d, TE_LONG_A) <= TE_LONG_DELTA || duration_diff!(d, TE_LONG_B) <= TE_LONG_DELTA
|
||||
}
|
||||
|
||||
/// Reverse the low 6 bits of `value` (chrysler_v0_reverse6).
|
||||
fn reverse6(value: u32) -> u8 {
|
||||
let mut out: u8 = 0;
|
||||
let mut v = value;
|
||||
for _ in 0..6 {
|
||||
out = (out << 1) | ((v & 1) as u8);
|
||||
v >>= 1;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Transform 9 bytes with the seed-derived XOR mask (chrysler_v0_transform_block).
|
||||
/// `button == 1` (Lock) flips a nibble of the mask depending on the seed parity.
|
||||
fn transform_block(input: &[u8; 9], key: u8, button: u8) -> [u8; 9] {
|
||||
let mut mask = XOR_TABLE[(key & 0x0F) as usize];
|
||||
if button == 1 {
|
||||
mask ^= if (key & 1) != 0 { 0xF0 } else { 0x0F };
|
||||
}
|
||||
let mut out = [0u8; 9];
|
||||
for i in 0..9 {
|
||||
out[i] = input[i] ^ mask;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Port of chrysler_v0_decode_packet: extract seed/button/check_ok/serial/counter from the
|
||||
/// 64-bit `data` (payload bytes 0..7) plus the 16-bit `data_2` (payload bytes 8,9).
|
||||
fn decode_packet(data: u64, data_2: u16) -> Decoded {
|
||||
let key = data.to_be_bytes();
|
||||
let key2 = data_2;
|
||||
let seed = Self::reverse6((key[0] >> 2) as u32);
|
||||
|
||||
let b1_xor_b6 = key[6] ^ key[1];
|
||||
let msb_set = (key[0] & 0x80) != 0;
|
||||
|
||||
let mut check_ok;
|
||||
let mut button: u8;
|
||||
|
||||
if msb_set {
|
||||
let key2_low = (key2 & 0xFF) as u8;
|
||||
check_ok = (key[1] == key[5]) && (b1_xor_b6 == 0x62);
|
||||
button = if (key2_low ^ key[4]) == 0x10 { 2 } else { 1 };
|
||||
} else {
|
||||
check_ok = false;
|
||||
button = 1;
|
||||
|
||||
if (key[1] ^ 0xC3) == key[5] {
|
||||
if b1_xor_b6 == 0x04 {
|
||||
check_ok = true;
|
||||
} else {
|
||||
check_ok = b1_xor_b6 == 0x08;
|
||||
if b1_xor_b6 == 0x08 {
|
||||
button = 2;
|
||||
}
|
||||
}
|
||||
} else if b1_xor_b6 == 0x08 {
|
||||
button = 2;
|
||||
}
|
||||
// (b1_xor_b6 == 0x04 with mismatched key[5]: button stays 1, check_ok stays false.)
|
||||
}
|
||||
|
||||
let encoded: [u8; 9] = [
|
||||
key[1],
|
||||
key[2],
|
||||
key[3],
|
||||
key[4],
|
||||
key[5],
|
||||
key[6],
|
||||
key[7],
|
||||
(key2 >> 8) as u8,
|
||||
(key2 & 0xFF) as u8,
|
||||
];
|
||||
let decoded = Self::transform_block(&encoded, seed, button);
|
||||
|
||||
let (serial, counter) = if (seed & 1) != 0 {
|
||||
// Payload B: serial only.
|
||||
let sn_b = ((decoded[0] as u32) << 24)
|
||||
| ((decoded[1] as u32) << 16)
|
||||
| ((decoded[2] as u32) << 8)
|
||||
| (decoded[7] as u32);
|
||||
(sn_b, 0u32)
|
||||
} else {
|
||||
// Payload A: serial (SnA) + rolling counter.
|
||||
let sn_a = ((decoded[0] as u32) << 24)
|
||||
| ((decoded[1] as u32) << 16)
|
||||
| ((decoded[2] as u32) << 8)
|
||||
| (decoded[3] as u32);
|
||||
let cnt = sn_a;
|
||||
(sn_a, cnt)
|
||||
};
|
||||
|
||||
Decoded {
|
||||
check_ok,
|
||||
button,
|
||||
seed,
|
||||
serial,
|
||||
counter,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a DecodedSignal from a committed frame, if the structural check passes.
|
||||
fn commit(&self) -> Option<DecodedSignal> {
|
||||
let d = Self::decode_packet(self.decode_data, self.data_2);
|
||||
if !d.check_ok {
|
||||
return None;
|
||||
}
|
||||
Some(DecodedSignal {
|
||||
serial: Some(d.serial),
|
||||
button: Some(d.button),
|
||||
counter: Some((d.counter & 0xFFFF) as u16),
|
||||
crc_valid: d.check_ok,
|
||||
// 80-bit frame: report the most-significant 64 bits (payload bytes 0..7).
|
||||
data: self.decode_data,
|
||||
data_count_bit: DECODE_BIT_COUNT,
|
||||
encoder_capable: true,
|
||||
// Stash the low 16 bits (data_2 = payload bytes 8,9) for the encoder.
|
||||
extra: Some(self.data_2 as u64),
|
||||
protocol_display_name: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Map a KAT generic button to a Chrysler button code (1=Lock, 2=Unlock).
|
||||
fn map_button(button: u8) -> u8 {
|
||||
match button {
|
||||
0x01 => 1, // Lock
|
||||
0x02 => 2, // Unlock
|
||||
0x04 => 1, // Trunk → Lock (Chrysler V0 only models Lock/Unlock)
|
||||
0x08 => 2, // Panic → Unlock
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read one bit out of an MSB-first payload (chrysler_v0_payload_get_bit).
|
||||
fn payload_get_bit(payload: &[u8; 10], index: u8) -> u8 {
|
||||
let byte = payload[(index >> 3) as usize];
|
||||
let shift = 7 - (index & 7);
|
||||
(byte >> shift) & 1
|
||||
}
|
||||
|
||||
/// Build a 10-byte payload from the 9 plaintext bytes (chrysler_v0_build_payload).
|
||||
fn build_payload(plain: &[u8; 9], counter: u8, button: u8, header_low2: u8) -> [u8; 10] {
|
||||
let transformed = Self::transform_block(plain, counter, button);
|
||||
let mut out = [0u8; 10];
|
||||
out[0] = (Self::reverse6(counter as u32) << 2) | (header_low2 & 0x03);
|
||||
out[1..10].copy_from_slice(&transformed);
|
||||
out
|
||||
}
|
||||
|
||||
/// ADD_LEVEL-style merge: combine adjacent same-level pulses.
|
||||
fn add_level(signal: &mut Vec<LevelDuration>, level: bool, duration: u32) {
|
||||
if let Some(last) = signal.last_mut() {
|
||||
if last.level == level {
|
||||
*last = LevelDuration::new(level, last.duration_us + duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
signal.push(LevelDuration::new(level, duration));
|
||||
}
|
||||
|
||||
/// Emit one 80-bit PWM payload frame (without the leading preamble).
|
||||
fn emit_payload(signal: &mut Vec<LevelDuration>, payload: &[u8; 10]) {
|
||||
for bit in 0..80u8 {
|
||||
let value = Self::payload_get_bit(payload, bit);
|
||||
if value != 0 {
|
||||
Self::add_level(signal, true, TE_ONE_SHORT);
|
||||
Self::add_level(signal, false, TE_LONG_A);
|
||||
} else {
|
||||
Self::add_level(signal, true, TE_SHORT);
|
||||
Self::add_level(signal, false, TE_LONG_B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit the preamble (24 short-HIGH + long_b-LOW pairs).
|
||||
fn emit_preamble(signal: &mut Vec<LevelDuration>) {
|
||||
for _ in 0..PREAMBLE_PAIRS {
|
||||
Self::add_level(signal, true, TE_SHORT);
|
||||
Self::add_level(signal, false, TE_LONG_B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for ChryslerV0Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Chrysler V0"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG_A,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: DECODE_BIT_COUNT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[315_000_000, 433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.packet_bit_count = 0;
|
||||
self.te_last = 0;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.data_2 = 0;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if level && Self::is_short(duration) {
|
||||
self.packet_bit_count = 0;
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::Seek;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Seek => {
|
||||
if level {
|
||||
self.te_last = duration;
|
||||
return None;
|
||||
}
|
||||
|
||||
if Self::is_long_mark(duration) {
|
||||
if Self::is_short(self.te_last) {
|
||||
self.packet_bit_count += 1;
|
||||
} else if self.packet_bit_count > 0x0F {
|
||||
self.data_2 = 0;
|
||||
self.step = DecoderStep::Data;
|
||||
self.decode_data = 1;
|
||||
self.decode_count_bit = 1;
|
||||
} else {
|
||||
self.packet_bit_count = 0;
|
||||
self.step = DecoderStep::Seek;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if duration > TE_GAP && self.packet_bit_count > 0x0F {
|
||||
self.decode_data = 0;
|
||||
self.data_2 = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.step = DecoderStep::Data;
|
||||
return None;
|
||||
}
|
||||
|
||||
self.step = DecoderStep::Reset;
|
||||
self.packet_bit_count = 0;
|
||||
}
|
||||
|
||||
DecoderStep::Data => {
|
||||
if level {
|
||||
self.te_last = duration;
|
||||
return None;
|
||||
}
|
||||
|
||||
let count = self.decode_count_bit;
|
||||
|
||||
if duration > TE_GAP {
|
||||
let result = if count as usize > 0x4F { self.commit() } else { None };
|
||||
self.step = DecoderStep::Reset;
|
||||
self.packet_bit_count = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
let bit_value: u8;
|
||||
if self.te_last < TE_SHORT {
|
||||
if !Self::is_short(self.te_last) || !Self::is_long_mark(duration) {
|
||||
let result = if count as usize > 0x4F { self.commit() } else { None };
|
||||
self.step = DecoderStep::Reset;
|
||||
self.packet_bit_count = 0;
|
||||
return result;
|
||||
}
|
||||
bit_value = 1;
|
||||
} else {
|
||||
if self.te_last > 0x2EE || !Self::is_long_mark(duration) {
|
||||
let result = if count as usize > 0x4F { self.commit() } else { None };
|
||||
self.step = DecoderStep::Reset;
|
||||
self.packet_bit_count = 0;
|
||||
return result;
|
||||
}
|
||||
bit_value = if Self::is_short(self.te_last) { 1 } else { 0 };
|
||||
}
|
||||
|
||||
let bit = (bit_value ^ 1) as u64;
|
||||
let new_count = count.wrapping_add(1);
|
||||
if count <= 0x3F {
|
||||
self.decode_data = (self.decode_data << 1) | bit;
|
||||
self.decode_count_bit = new_count;
|
||||
return None;
|
||||
}
|
||||
|
||||
self.data_2 = (self.data_2 << 1) | (bit as u16);
|
||||
self.decode_count_bit = new_count;
|
||||
if new_count as usize != DECODE_BIT_COUNT {
|
||||
return None;
|
||||
}
|
||||
|
||||
let result = self.commit();
|
||||
self.decode_data = 0;
|
||||
self.data_2 = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.step = DecoderStep::Reset;
|
||||
self.packet_bit_count = 0;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
// Rebuild the 10-byte payload A (high 64 bits + low 16 bits) from the decoded frame,
|
||||
// re-derive seed/plaintext, then emit preamble + dual PWM frames (matches
|
||||
// chrysler_v0_build_upload, ENABLE_EMULATE_FEATURE).
|
||||
let data = decoded.data;
|
||||
let data_2 = decoded.extra.unwrap_or(0) as u16;
|
||||
let key = data.to_be_bytes();
|
||||
let key2 = data_2;
|
||||
|
||||
// Header low-2 bits live in the top byte's low nibble (matches encoder deserialize:
|
||||
// plain_header = (data >> 56) & 0x03).
|
||||
let header_low2 = (key[0]) & 0x03;
|
||||
let seed = Self::reverse6((key[0] >> 2) as u32);
|
||||
|
||||
// Determine the originally-transmitted button (matches encoder deserialize).
|
||||
let original_button: u8 = if (key[0] & 0x80) == 0 {
|
||||
if (key[1] ^ key[6]) == 0x08 { 2 } else { 1 }
|
||||
} else if (((key2 & 0xFF) as u8) ^ key[4]) == 0x10 {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
// Recover the plaintext (Plain_A/Plain_B both default to this in the C when no explicit
|
||||
// Plain_A/B is stored — which is our case).
|
||||
let encoded: [u8; 9] = [
|
||||
key[1],
|
||||
key[2],
|
||||
key[3],
|
||||
key[4],
|
||||
key[5],
|
||||
key[6],
|
||||
key[7],
|
||||
(key2 >> 8) as u8,
|
||||
(key2 & 0xFF) as u8,
|
||||
];
|
||||
let generated = Self::transform_block(&encoded, seed, original_button);
|
||||
let mut plain_a = generated;
|
||||
let mut plain_b = generated;
|
||||
|
||||
// Apply the requested button (KAT button → Chrysler 1/2).
|
||||
let tx_button = Self::map_button(button);
|
||||
if tx_button != original_button {
|
||||
plain_a[5] ^= 0x0C;
|
||||
plain_b[3] ^= 0x30;
|
||||
}
|
||||
|
||||
// Counter handling (matches encoder deserialize): counter_a is the even counter, counter_b
|
||||
// is counter_a - 1 (wrapping in 6 bits). Derive the base counter from the seed.
|
||||
let counter = (seed as u32) & 0x3F;
|
||||
let mut counter_a = (counter & 0x3F) as u8;
|
||||
if (counter_a & 1) != 0 {
|
||||
counter_a = counter_a.wrapping_sub(1) & 0x3F;
|
||||
}
|
||||
let counter_b = if counter_a == 0 { 0x3F } else { counter_a - 1 };
|
||||
|
||||
let payload_a = Self::build_payload(&plain_a, counter_a, tx_button, header_low2);
|
||||
let payload_b = Self::build_payload(&plain_b, counter_b, tx_button, header_low2);
|
||||
|
||||
let mut signal = Vec::with_capacity(PREAMBLE_PAIRS * 4 + 80 * 4 + 16);
|
||||
|
||||
// Frame A: preamble, short + frame_gap, payload A, short + frame_gap.
|
||||
Self::emit_preamble(&mut signal);
|
||||
Self::add_level(&mut signal, true, TE_SHORT);
|
||||
Self::add_level(&mut signal, false, FRAME_GAP);
|
||||
Self::emit_payload(&mut signal, &payload_a);
|
||||
Self::add_level(&mut signal, true, TE_SHORT);
|
||||
Self::add_level(&mut signal, false, FRAME_GAP);
|
||||
|
||||
// Frame B: preamble, short + frame_gap, payload B, short + frame_gap.
|
||||
Self::emit_preamble(&mut signal);
|
||||
Self::add_level(&mut signal, true, TE_SHORT);
|
||||
Self::add_level(&mut signal, false, FRAME_GAP);
|
||||
Self::emit_payload(&mut signal, &payload_b);
|
||||
Self::add_level(&mut signal, true, TE_SHORT);
|
||||
Self::add_level(&mut signal, false, FRAME_GAP);
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChryslerV0Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Decode a stream of pairs with a fresh decoder, returning the first frame.
|
||||
fn decode_stream(pairs: &[LevelDuration]) -> Option<DecodedSignal> {
|
||||
let mut dec = ChryslerV0Decoder::new();
|
||||
for p in pairs {
|
||||
if let Some(sig) = dec.feed(p.level, p.duration_us) {
|
||||
return Some(sig);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// reverse6 is its own inverse on 6-bit values.
|
||||
#[test]
|
||||
fn reverse6_is_involution() {
|
||||
for v in 0u32..64 {
|
||||
let r = ChryslerV0Decoder::reverse6(v) as u32;
|
||||
assert_eq!(ChryslerV0Decoder::reverse6(r) as u32, v, "reverse6 not involutive at {v}");
|
||||
}
|
||||
}
|
||||
|
||||
/// transform_block is its own inverse for a given (seed, button) — XOR with a constant mask.
|
||||
#[test]
|
||||
fn transform_block_round_trips() {
|
||||
let plain: [u8; 9] = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99];
|
||||
for &button in &[1u8, 2u8] {
|
||||
for seed in 0u8..64 {
|
||||
let enc = ChryslerV0Decoder::transform_block(&plain, seed, button);
|
||||
let dec = ChryslerV0Decoder::transform_block(&enc, seed, button);
|
||||
assert_eq!(dec, plain, "transform not invertible seed={seed} button={button}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a payload-A frame exactly as the encoder does (even counter, MSB-clear path), encode
|
||||
/// it to a PWM burst, decode it back, and confirm the frame, check_ok, button and serial all
|
||||
/// survive the round trip. Payload A is the first frame the encoder emits, so `decode_stream`
|
||||
/// returns it.
|
||||
///
|
||||
/// Payload-A check_ok requires (in the stored = transformed bytes): `(key[1]^0xC3)==key[5]`
|
||||
/// and `key[6]^key[1]==0x04`. Since transform XORs every byte with a constant mask, this is
|
||||
/// equivalent to a check on the *plaintext*: `plain[0]^0xC3==plain[4]` and `plain[5]^plain[0]==0x04`.
|
||||
#[test]
|
||||
fn encode_decode_round_trip() {
|
||||
// Choose an even counter so the encoder's counter_a equals it (exact round trip), and pick
|
||||
// plaintext satisfying the payload-A invariants.
|
||||
let counter_a: u8 = 0x14; // even
|
||||
let button: u8 = 1; // Lock (matches the b1^b6==0x04 branch which keeps button=1)
|
||||
let header_low2: u8 = 0x02;
|
||||
|
||||
let p0 = 0x5Au8;
|
||||
let mut plain = [0x00u8; 9];
|
||||
plain[0] = p0;
|
||||
plain[1] = 0x11;
|
||||
plain[2] = 0x22;
|
||||
plain[3] = 0x33;
|
||||
plain[4] = p0 ^ 0xC3; // plain[0]^0xC3 == plain[4]
|
||||
plain[5] = p0 ^ 0x04; // plain[5]^plain[0] == 0x04
|
||||
plain[6] = 0x66;
|
||||
plain[7] = 0x77;
|
||||
plain[8] = 0x88;
|
||||
|
||||
// Build the 10-byte payload the way the encoder/transmitter does.
|
||||
let payload = ChryslerV0Decoder::build_payload(&plain, counter_a, button, header_low2);
|
||||
let data = u64::from_be_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3], payload[4], payload[5], payload[6],
|
||||
payload[7],
|
||||
]);
|
||||
let data_2: u16 = ((payload[8] as u16) << 8) | payload[9] as u16;
|
||||
|
||||
// Sanity: the raw frame must pass the structural check before we exercise the codec.
|
||||
let d = ChryslerV0Decoder::decode_packet(data, data_2);
|
||||
assert!(d.check_ok, "constructed payload-A vector should satisfy check_ok");
|
||||
assert_eq!(d.button, button, "constructed button mismatch");
|
||||
assert_eq!(d.seed & 1, 0, "payload A must have an even seed");
|
||||
|
||||
let decoded = DecodedSignal {
|
||||
serial: Some(d.serial),
|
||||
button: Some(d.button),
|
||||
counter: Some((d.counter & 0xFFFF) as u16),
|
||||
crc_valid: true,
|
||||
data,
|
||||
data_count_bit: DECODE_BIT_COUNT,
|
||||
encoder_capable: true,
|
||||
extra: Some(data_2 as u64),
|
||||
protocol_display_name: None,
|
||||
};
|
||||
|
||||
let dec = ChryslerV0Decoder::new();
|
||||
// Encode with the same (Lock) button so the plaintext is not perturbed.
|
||||
let burst = dec.encode(&decoded, 0x01).expect("encode should succeed");
|
||||
assert!(!burst.is_empty());
|
||||
|
||||
let got = decode_stream(&burst).expect("encoded burst should decode");
|
||||
assert!(got.crc_valid, "decoded frame should pass check");
|
||||
// The high 64 bits (payload bytes 0..7) must reproduce the original frame.
|
||||
assert_eq!(got.data, data, "round-trip data mismatch");
|
||||
assert_eq!(got.button, decoded.button, "button mismatch");
|
||||
assert_eq!(got.serial, decoded.serial, "serial mismatch");
|
||||
}
|
||||
}
|
||||
+39
-49
@@ -4,9 +4,9 @@
|
||||
//! `fiat_v0.h`. Preamble: count short pulses (HIGH or LOW, 200±100µs); when preamble_count >= 150
|
||||
//! (0x96), accept 800µs LOW gap (gap_threshold 800, te_delta 100) and enter Data. Data: 64 bits
|
||||
//! (serial=data_low, cnt=data_high) then 7 more bits; complete when bit_count > 0x46 with
|
||||
//! btn = (data_low << 1) | 1, 71 bits total. Encoder: differential Manchester, 150 preamble pairs,
|
||||
//! last LOW replaced by 800µs gap; 64 data bits then 6 btn bits (btn_to_send = btn >> 1); end
|
||||
//! marker te_short*8 LOW.
|
||||
//! btn = (uint8_t)data_low, 71 bits total. Encoder: standard Manchester, 150 preamble pairs,
|
||||
//! last LOW replaced by 800µs gap; 64 data bits then 7 endbyte bits (endbyte & 0x7F); end
|
||||
//! marker te_short*4 LOW.
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
@@ -294,8 +294,9 @@ impl ProtocolDecoder for FiatV0Decoder {
|
||||
self.data_high = 0;
|
||||
}
|
||||
|
||||
if self.bit_count > 0x46 {
|
||||
self.btn = ((self.data_low << 1) | 1) as u8;
|
||||
if self.bit_count == 0x47 {
|
||||
// C: endbyte = (uint8_t)data_low (no transform)
|
||||
self.btn = self.data_low as u8;
|
||||
let result = self.parse_data();
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
@@ -304,6 +305,19 @@ impl ProtocolDecoder for FiatV0Decoder {
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Manchester reset event (gap path) — C extracts at exactly 71 bits
|
||||
if self.bit_count == 0x47 {
|
||||
self.btn = self.data_low as u8;
|
||||
let result = self.parse_data();
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.step = DecoderStep::Reset;
|
||||
return Some(result);
|
||||
} else if self.bit_count < 0x40 {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
self.te_last = duration;
|
||||
}
|
||||
@@ -319,80 +333,56 @@ impl ProtocolDecoder for FiatV0Decoder {
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let serial = decoded.serial?;
|
||||
let cnt = decoded.counter.unwrap_or(0) as u32;
|
||||
let btn = decoded.button.unwrap_or(0).max(button);
|
||||
let endbyte = if button != 0 { button } else { decoded.button.unwrap_or(0) };
|
||||
|
||||
// Ref: data = (cnt<<32)|serial; btn_to_send = btn >> 1 (reverse decoder's (x<<1)|1)
|
||||
// C: data = (hop << 32) | fix; endbyte sent as-is (7 bits, & 0x7F)
|
||||
let data = ((cnt as u64) << 32) | (serial as u64);
|
||||
let btn_to_send = btn >> 1;
|
||||
let te_short = TE_SHORT;
|
||||
|
||||
let mut signal = Vec::with_capacity(1024);
|
||||
let te_short = TE_SHORT;
|
||||
let te_long = TE_LONG;
|
||||
|
||||
for burst in 0..TOTAL_BURSTS {
|
||||
if burst > 0 {
|
||||
signal.push(LevelDuration::new(false, INTER_BURST_GAP));
|
||||
}
|
||||
|
||||
// Preamble: HIGH-LOW pairs; last LOW replaced by gap (ref)
|
||||
for i in 0..PREAMBLE_PAIRS {
|
||||
// Preamble: alternating short pulses; last LOW extended to gap
|
||||
for _ in 0..PREAMBLE_PAIRS {
|
||||
signal.push(LevelDuration::new(true, te_short));
|
||||
signal.push(LevelDuration::new(
|
||||
false,
|
||||
if i == PREAMBLE_PAIRS - 1 { GAP_US } else { te_short },
|
||||
));
|
||||
signal.push(LevelDuration::new(false, te_short));
|
||||
}
|
||||
// Extend last LOW to create gap (matches C: upload[index-1] = gap)
|
||||
if let Some(last) = signal.last_mut() {
|
||||
*last = LevelDuration::new(false, GAP_US);
|
||||
}
|
||||
|
||||
// First bit (bit 63) - differential Manchester
|
||||
let first_bit = (data >> 63) & 1 == 1;
|
||||
if first_bit {
|
||||
signal.push(LevelDuration::new(true, te_long));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(true, te_short));
|
||||
signal.push(LevelDuration::new(false, te_long));
|
||||
}
|
||||
|
||||
let mut prev_bit = first_bit;
|
||||
|
||||
// Remaining 63 data bits
|
||||
for bit in (0..63).rev() {
|
||||
// Standard Manchester encode 64 bits of data (matches C)
|
||||
for bit in (0..64).rev() {
|
||||
let curr_bit = (data >> bit) & 1 == 1;
|
||||
if !prev_bit && !curr_bit {
|
||||
if curr_bit {
|
||||
signal.push(LevelDuration::new(true, te_short));
|
||||
signal.push(LevelDuration::new(false, te_short));
|
||||
} else if !prev_bit && curr_bit {
|
||||
signal.push(LevelDuration::new(true, te_long));
|
||||
} else if prev_bit && !curr_bit {
|
||||
signal.push(LevelDuration::new(false, te_long));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(false, te_short));
|
||||
signal.push(LevelDuration::new(true, te_short));
|
||||
}
|
||||
prev_bit = curr_bit;
|
||||
}
|
||||
|
||||
// 6 btn bits (ref: for bit 5 down to 0)
|
||||
for bit in (0..6).rev() {
|
||||
let curr_bit = (btn_to_send >> bit) & 1 == 1;
|
||||
if !prev_bit && !curr_bit {
|
||||
// Standard Manchester encode 7 bits of endbyte (matches C: endbyte & 0x7F, bits 6..0)
|
||||
let endbyte_masked = endbyte & 0x7F;
|
||||
for bit in (0..7).rev() {
|
||||
let curr_bit = (endbyte_masked >> bit) & 1 == 1;
|
||||
if curr_bit {
|
||||
signal.push(LevelDuration::new(true, te_short));
|
||||
signal.push(LevelDuration::new(false, te_short));
|
||||
} else if !prev_bit && curr_bit {
|
||||
signal.push(LevelDuration::new(true, te_long));
|
||||
} else if prev_bit && !curr_bit {
|
||||
signal.push(LevelDuration::new(false, te_long));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(false, te_short));
|
||||
signal.push(LevelDuration::new(true, te_short));
|
||||
}
|
||||
prev_bit = curr_bit;
|
||||
}
|
||||
|
||||
// End marker (ref)
|
||||
if prev_bit {
|
||||
signal.push(LevelDuration::new(false, te_short));
|
||||
}
|
||||
signal.push(LevelDuration::new(false, te_short * 8));
|
||||
// End marker: te_short * 4 LOW (matches C)
|
||||
signal.push(LevelDuration::new(false, te_short * 4));
|
||||
}
|
||||
|
||||
Some(signal)
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
//! Fiat V1 protocol decoder (Magneti Marelli BSI / PCF7946)
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/fiat_v1.c` and
|
||||
//! `fiat_v1.h`. Found on Fiat Panda, Grande Punto, and possibly other Fiat/Lancia/Alfa ~2003-2012.
|
||||
//!
|
||||
//! RF: 433.92 MHz, Manchester encoding.
|
||||
//! Two timing variants with identical frame structure:
|
||||
//! Type A (e.g. Panda): te_short ~260us, te_long ~520us
|
||||
//! Type B (e.g. Grande Punto): te_short ~100us, te_long ~200us
|
||||
//! TE is auto-detected from preamble pulse averaging (boundary at 180us).
|
||||
//!
|
||||
//! Frame layout (103-104 bits = 13 bytes):
|
||||
//! Bytes 0-1: 0xFFFF/0xFFFC preamble residue
|
||||
//! Bytes 2-5: Serial (32 bits)
|
||||
//! Byte 6: [Button:4 | Epoch:4]
|
||||
//! Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
|
||||
//! Bytes 8-12: Encrypted payload (40 bits)
|
||||
//!
|
||||
//! State machine: Reset -> Preamble -> Sync -> Data (-> RetxSync -> Data)
|
||||
//! No encoder (decode-only).
|
||||
//!
|
||||
//! Original C implementation by @lupettohf
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
// Default timing constants (Type A)
|
||||
const TE_SHORT: u32 = 260;
|
||||
#[allow(dead_code)]
|
||||
const TE_LONG: u32 = 520;
|
||||
#[allow(dead_code)]
|
||||
const TE_DELTA: u32 = 80;
|
||||
#[allow(dead_code)]
|
||||
const MIN_COUNT_BIT: usize = 80;
|
||||
|
||||
// Preamble / sync / data constants (from C defines)
|
||||
const PREAMBLE_PULSE_MIN: u32 = 50;
|
||||
const PREAMBLE_PULSE_MAX: u32 = 350;
|
||||
const PREAMBLE_MIN: u16 = 80;
|
||||
const MAX_DATA_BITS: u8 = 104;
|
||||
const MIN_DATA_BITS: u8 = 80;
|
||||
const GAP_TE_MULT: u32 = 4;
|
||||
const SYNC_TE_MIN_MULT: u32 = 4;
|
||||
const SYNC_TE_MAX_MULT: u32 = 12;
|
||||
const RETX_GAP_MIN: u32 = 5000;
|
||||
const RETX_SYNC_MIN: u32 = 400;
|
||||
const RETX_SYNC_MAX: u32 = 2800;
|
||||
#[allow(dead_code)]
|
||||
const TE_TYPE_AB_BOUNDARY: u32 = 180;
|
||||
|
||||
/// Manchester state machine (Flipper-style, same as fiat_v0.rs / common.rs).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0 = 0,
|
||||
Mid1 = 1,
|
||||
Start0 = 2,
|
||||
Start1 = 3,
|
||||
}
|
||||
|
||||
/// Decoder state machine steps.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Preamble,
|
||||
Sync,
|
||||
Data,
|
||||
RetxSync,
|
||||
}
|
||||
|
||||
pub struct FiatV1Decoder {
|
||||
step: DecoderStep,
|
||||
manchester_state: ManchesterState,
|
||||
|
||||
// Preamble tracking
|
||||
preamble_count: u16,
|
||||
|
||||
// Auto-detected TE from preamble averaging
|
||||
te_sum: u32,
|
||||
te_count: u16,
|
||||
te_detected: u32,
|
||||
|
||||
// Data accumulation
|
||||
raw_data: [u8; 13],
|
||||
bit_count: u8,
|
||||
data: u64, // first 64 bits
|
||||
extra_data: u32, // bits 65+
|
||||
|
||||
// Parsed fields
|
||||
serial: u32,
|
||||
btn: u8,
|
||||
cnt: u8,
|
||||
|
||||
te_last: u32,
|
||||
}
|
||||
|
||||
impl FiatV1Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
preamble_count: 0,
|
||||
te_sum: 0,
|
||||
te_count: 0,
|
||||
te_detected: 0,
|
||||
raw_data: [0u8; 13],
|
||||
bit_count: 0,
|
||||
data: 0,
|
||||
extra_data: 0,
|
||||
serial: 0,
|
||||
btn: 0,
|
||||
cnt: 0,
|
||||
te_last: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance the Manchester state machine by one event.
|
||||
/// Event encoding: 0=ShortLow, 1=ShortHigh, 2=LongLow, 3=LongHigh.
|
||||
/// Returns `Some(bit)` when a data bit is emitted, `None` otherwise.
|
||||
fn manchester_advance(&mut self, event: u8) -> Option<bool> {
|
||||
let (new_state, emit) = match (self.manchester_state, event) {
|
||||
(ManchesterState::Mid0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 1) => (ManchesterState::Start1, true),
|
||||
(ManchesterState::Mid0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 3) => (ManchesterState::Mid1, true),
|
||||
|
||||
(ManchesterState::Mid1, 0) => (ManchesterState::Start0, true),
|
||||
(ManchesterState::Mid1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Mid1, 2) => (ManchesterState::Mid0, true),
|
||||
(ManchesterState::Mid1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 1) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start1, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Start1, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
_ => (ManchesterState::Mid1, false),
|
||||
};
|
||||
|
||||
self.manchester_state = new_state;
|
||||
if emit {
|
||||
Some((event & 1) == 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset Manchester state machine (to Mid1, matching C reset).
|
||||
fn manchester_reset(&mut self) {
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
|
||||
/// Prepare for data reception: clear accumulators, reset Manchester, enter Data step.
|
||||
/// Matches `fiat_marelli_prepare_data` in the C reference.
|
||||
fn prepare_data(&mut self) {
|
||||
self.bit_count = 0;
|
||||
self.extra_data = 0;
|
||||
self.data = 0;
|
||||
self.raw_data = [0u8; 13];
|
||||
self.manchester_reset();
|
||||
self.step = DecoderStep::Data;
|
||||
}
|
||||
|
||||
/// Get the effective te_short (auto-detected or default).
|
||||
fn te_short(&self) -> u32 {
|
||||
if self.te_detected != 0 {
|
||||
self.te_detected
|
||||
} else {
|
||||
TE_SHORT
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the effective te_long (2 * te_short).
|
||||
fn te_long(&self) -> u32 {
|
||||
self.te_short() * 2
|
||||
}
|
||||
|
||||
/// Get the effective te_delta (te_short / 2, minimum 30).
|
||||
fn te_delta(&self) -> u32 {
|
||||
let d = self.te_short() / 2;
|
||||
if d < 30 { 30 } else { d }
|
||||
}
|
||||
|
||||
/// Map button code to name (matches C fiat_marelli_button_name).
|
||||
#[allow(dead_code)]
|
||||
fn button_name(btn: u8) -> &'static str {
|
||||
match btn {
|
||||
0x7 => "Lock",
|
||||
0xB => "Unlock",
|
||||
0xD => "Trunk",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a DecodedSignal from the current state.
|
||||
fn parse_data(&self) -> DecodedSignal {
|
||||
// Pack extra bytes (bits 65+) into extra field
|
||||
let extra = if self.bit_count > 64 {
|
||||
Some(self.extra_data as u64)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(self.serial),
|
||||
button: Some(self.btn),
|
||||
counter: Some(self.cnt as u16),
|
||||
crc_valid: true, // no CRC defined for this protocol
|
||||
data: self.data,
|
||||
data_count_bit: self.bit_count as usize,
|
||||
encoder_capable: false,
|
||||
extra,
|
||||
protocol_display_name: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for FiatV1Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Fiat V1"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: MIN_COUNT_BIT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.preamble_count = 0;
|
||||
self.bit_count = 0;
|
||||
self.extra_data = 0;
|
||||
self.te_last = 0;
|
||||
self.te_sum = 0;
|
||||
self.te_count = 0;
|
||||
self.te_detected = 0;
|
||||
self.data = 0;
|
||||
self.raw_data = [0u8; 13];
|
||||
self.serial = 0;
|
||||
self.btn = 0;
|
||||
self.cnt = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
let te_short = self.te_short();
|
||||
let te_long = self.te_long();
|
||||
let te_delta = self.te_delta();
|
||||
|
||||
match self.step {
|
||||
// =========================================================
|
||||
// Reset: wait for a HIGH pulse in preamble range, or detect
|
||||
// retransmission gap (LOW > 5000us with valid te_detected).
|
||||
// =========================================================
|
||||
DecoderStep::Reset => {
|
||||
if level {
|
||||
if duration >= PREAMBLE_PULSE_MIN && duration <= PREAMBLE_PULSE_MAX {
|
||||
self.step = DecoderStep::Preamble;
|
||||
self.preamble_count = 1;
|
||||
self.te_sum = duration;
|
||||
self.te_count = 1;
|
||||
self.te_last = duration;
|
||||
}
|
||||
} else if duration > RETX_GAP_MIN && self.te_detected != 0 {
|
||||
self.step = DecoderStep::RetxSync;
|
||||
self.te_last = duration;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Preamble: accumulate pulses in 50-350us range for TE
|
||||
// averaging. When count >= 80 and we see a LOW gap >= 4*TE,
|
||||
// transition to Sync.
|
||||
// =========================================================
|
||||
DecoderStep::Preamble => {
|
||||
if duration >= PREAMBLE_PULSE_MIN && duration <= PREAMBLE_PULSE_MAX {
|
||||
// Pulse in valid preamble range (either HIGH or LOW)
|
||||
self.preamble_count += 1;
|
||||
self.te_sum += duration;
|
||||
self.te_count += 1;
|
||||
self.te_last = duration;
|
||||
} else if !level {
|
||||
// LOW pulse outside preamble range -- check if it's the gap after preamble
|
||||
if self.preamble_count >= PREAMBLE_MIN && self.te_count > 0 {
|
||||
self.te_detected = self.te_sum / (self.te_count as u32);
|
||||
let gap_threshold = self.te_detected * GAP_TE_MULT;
|
||||
|
||||
if duration > gap_threshold {
|
||||
self.step = DecoderStep::Sync;
|
||||
self.te_last = duration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
// HIGH pulse outside preamble range
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Sync: expect HIGH sync pulse of te_detected*4 to
|
||||
// te_detected*12 duration. On match, prepare data decoder.
|
||||
// =========================================================
|
||||
DecoderStep::Sync => {
|
||||
let sync_min = self.te_detected * SYNC_TE_MIN_MULT;
|
||||
let sync_max = self.te_detected * SYNC_TE_MAX_MULT;
|
||||
|
||||
if level && duration >= sync_min && duration <= sync_max {
|
||||
self.prepare_data();
|
||||
self.te_last = duration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// RetxSync: after retransmission gap (>5000us LOW), look for
|
||||
// HIGH sync pulse in 400-2800us range.
|
||||
// =========================================================
|
||||
DecoderStep::RetxSync => {
|
||||
if level && duration >= RETX_SYNC_MIN && duration <= RETX_SYNC_MAX {
|
||||
self.prepare_data();
|
||||
self.te_last = duration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Data: Manchester decode up to 104 bits. Complete frame on
|
||||
// 104 bits reached or gap with >= 80 bits collected.
|
||||
// =========================================================
|
||||
DecoderStep::Data => {
|
||||
let mut event: u8 = 4; // 4 = ManchesterEventReset (invalid)
|
||||
let mut frame_complete = false;
|
||||
|
||||
// Check for short pulse match
|
||||
let diff_short = duration_diff!(duration, te_short);
|
||||
if diff_short < te_delta {
|
||||
// Flipper convention: level=HIGH -> ShortLow(0), level=LOW -> ShortHigh(1)
|
||||
event = if level { 0 } else { 1 };
|
||||
} else {
|
||||
// Check for long pulse match
|
||||
let diff_long = duration_diff!(duration, te_long);
|
||||
if diff_long < te_delta {
|
||||
event = if level { 2 } else { 3 };
|
||||
}
|
||||
}
|
||||
|
||||
if event != 4 {
|
||||
// Valid Manchester event
|
||||
if let Some(data_bit) = self.manchester_advance(event) {
|
||||
let new_bit: u32 = if data_bit { 1 } else { 0 };
|
||||
|
||||
// Store bit into raw_data byte array (MSB-first)
|
||||
if self.bit_count < MAX_DATA_BITS {
|
||||
let byte_idx = (self.bit_count / 8) as usize;
|
||||
let bit_pos = 7 - (self.bit_count % 8);
|
||||
if new_bit != 0 {
|
||||
self.raw_data[byte_idx] |= 1u8 << bit_pos;
|
||||
}
|
||||
}
|
||||
|
||||
// Also accumulate into u64 data / u32 extra_data
|
||||
if self.bit_count < 64 {
|
||||
self.data = (self.data << 1) | (new_bit as u64);
|
||||
} else {
|
||||
self.extra_data = (self.extra_data << 1) | new_bit;
|
||||
}
|
||||
|
||||
self.bit_count += 1;
|
||||
if self.bit_count >= MAX_DATA_BITS {
|
||||
frame_complete = true;
|
||||
}
|
||||
}
|
||||
} else if self.bit_count >= MIN_DATA_BITS {
|
||||
// Gap or invalid pulse but we have enough bits
|
||||
frame_complete = true;
|
||||
} else {
|
||||
// Not enough bits and invalid pulse -- abort
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
|
||||
if frame_complete {
|
||||
// Parse serial, button, counter from raw_data
|
||||
self.serial = ((self.raw_data[2] as u32) << 24)
|
||||
| ((self.raw_data[3] as u32) << 16)
|
||||
| ((self.raw_data[4] as u32) << 8)
|
||||
| (self.raw_data[5] as u32);
|
||||
self.btn = (self.raw_data[6] >> 4) & 0x0F;
|
||||
self.cnt = (self.raw_data[7] >> 3) & 0x1F;
|
||||
|
||||
let result = self.parse_data();
|
||||
self.step = DecoderStep::Reset;
|
||||
self.te_last = duration;
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
self.te_last = duration;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FiatV1Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
+23
-10
@@ -225,10 +225,26 @@ impl FordV0Decoder {
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// BS calculation
|
||||
// Checksum / BS calculation
|
||||
// =========================================================================
|
||||
|
||||
/// Calculate BS = (count_low_byte + bs_magic + (button << 4)) with overflow handling
|
||||
/// Calculate checksum from serial, count, and button (matches psa.c ford_v0_calculate_checksum).
|
||||
/// Sums all bytes of serial + all bytes of count + (button << 4), truncated to u8.
|
||||
fn calculate_checksum(serial: u32, count: u32, button: u8) -> u8 {
|
||||
let sum: u32 = ((count >> 24) & 0xFF)
|
||||
.wrapping_add((count >> 16) & 0xFF)
|
||||
.wrapping_add((count >> 8) & 0xFF)
|
||||
.wrapping_add(count & 0xFF)
|
||||
.wrapping_add((serial >> 24) & 0xFF)
|
||||
.wrapping_add((serial >> 16) & 0xFF)
|
||||
.wrapping_add((serial >> 8) & 0xFF)
|
||||
.wrapping_add(serial & 0xFF)
|
||||
.wrapping_add((button as u32) << 4);
|
||||
(sum & 0xFF) as u8
|
||||
}
|
||||
|
||||
/// Legacy BS calculation (kept for reference; encoder now uses calculate_checksum)
|
||||
#[allow(dead_code)]
|
||||
fn calculate_bs(count: u32, button: u8, bs_magic: u8) -> u8 {
|
||||
let result: u16 = (count as u16 & 0xFF)
|
||||
.wrapping_add(bs_magic as u16)
|
||||
@@ -651,21 +667,18 @@ impl ProtocolDecoder for FordV0Decoder {
|
||||
decoded.counter.unwrap_or(0) as u32
|
||||
}) & 0xFFFFF; // 20-bit
|
||||
|
||||
// Use stored bs_magic (or default to 0x6F for backward compatibility)
|
||||
let bs_magic = if self.bs_magic != 0 { self.bs_magic } else { 0x6F };
|
||||
|
||||
// Calculate BS from count + button + bs_magic (matches C ford_v0_calculate_bs)
|
||||
let bs = Self::calculate_bs(count, button, bs_magic);
|
||||
// Calculate checksum from serial + count + button (matches ford_v0_calculate_checksum in C)
|
||||
let checksum = Self::calculate_checksum(serial, count, button);
|
||||
|
||||
// Extract header byte from the original key1 (first byte)
|
||||
let header_byte = (decoded.data >> 56) as u8;
|
||||
|
||||
// Encode key1 from fields (same count, new button)
|
||||
let new_key1 = Self::encode_ford_v0(header_byte, serial, button, count, bs);
|
||||
let new_key1 = Self::encode_ford_v0(header_byte, serial, button, count, checksum);
|
||||
|
||||
// Calculate CRC for key2
|
||||
let crc = Self::calculate_crc_for_tx(new_key1, bs);
|
||||
let new_key2 = ((bs as u16) << 8) | (crc as u16);
|
||||
let crc = Self::calculate_crc_for_tx(new_key1, checksum);
|
||||
let new_key2 = ((checksum as u16) << 8) | (crc as u16);
|
||||
|
||||
// Build one 6-burst block and repeat TX_REPEAT times (matches reference encoder.repeat = 10)
|
||||
let single = Self::build_upload(new_key1, new_key2);
|
||||
|
||||
@@ -0,0 +1,778 @@
|
||||
//! Ford V1 protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/ford_v1.c` and `ford_v1.h`.
|
||||
//! Manchester 65/130µs (te_delta 39), FM. 136 bits / 17 bytes: key1 (bytes 0..7, 56 bits) +
|
||||
//! key2 (bytes 7..15, 64 bits) + CRC16 (bytes 15..16). Preamble ≥50 long pulses, then a short-pulse
|
||||
//! sync window (`sync_event_count > 2`) replays buffered Manchester events and enters the 17-byte
|
||||
//! data collection. This is a ROLLING-code protocol.
|
||||
//!
|
||||
//! Crypto: a proprietary parity-based descrambling cipher (`ford_v1_decode_with_flag`) operating on
|
||||
//! the 9-byte air block `raw[6..15]`, plus CRC16/CCITT (poly 0x1021, init 0x0000) over `raw[3..15]`.
|
||||
//! Emission is gated on CRC16 validity (with a 17-byte bit-inverted fallback) so it never
|
||||
//! false-matches. Encryption/rolling detection mirrors the C: a strict branch
|
||||
//! (`decoded[3]==raw[5] && decoded[4]==raw[6]`) yields plaintext serial/button/counter; otherwise an
|
||||
//! encode round-trip check classifies it as encrypted/rolling. Encoder supported (6 bursts).
|
||||
//!
|
||||
//! Manchester transition table is the Flipper differential-Manchester table (same as Ford V0/V2).
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 65;
|
||||
const TE_LONG: u32 = 130;
|
||||
const TE_DELTA: u32 = 39;
|
||||
const DATA_BITS: usize = 136;
|
||||
const DATA_BYTES: usize = 17;
|
||||
const PREAMBLE_MIN: u16 = 50;
|
||||
/// C uses FORD_V1_DELTA_LONG (40) only for the preamble long-pulse match; data/sync use te_delta (39).
|
||||
const DELTA_LONG: u32 = 40;
|
||||
const SILENCE_LONG_MULT: u32 = 3;
|
||||
|
||||
// Manchester event encoding (matches Flipper ManchesterEvent ordinals used in the C buffer):
|
||||
// 0 = ShortLow, 1 = ShortHigh, 2 = LongLow, 3 = LongHigh.
|
||||
const EV_SHORT_LOW: u8 = 0;
|
||||
const EV_SHORT_HIGH: u8 = 1;
|
||||
const EV_LONG_LOW: u8 = 2;
|
||||
const EV_LONG_HIGH: u8 = 3;
|
||||
|
||||
// Encoder constants (subghz_protocol_encoder_ford_v1).
|
||||
const ENC_BURST_COUNT: usize = 6;
|
||||
const ENC_PREAMBLE_PAIRS: usize = 400;
|
||||
const ENC_SYNC_SHORT_US: u32 = 65;
|
||||
const ENC_SYNC_LONG_US: u32 = 130;
|
||||
const ENC_GAP_REPEAT_US: u32 = 50000;
|
||||
const ENC_GAP_LAST_US: u32 = 260;
|
||||
/// Per-burst override of pkt[4] (matches ford_v1_encoder_burst_pkt4_vals).
|
||||
const ENC_BURST_PKT4: [u8; 6] = [0x08, 0x00, 0x10, 0x08, 0x00, 0x10];
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0 = 0,
|
||||
Mid1 = 1,
|
||||
Start0 = 2,
|
||||
Start1 = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Preamble,
|
||||
Sync,
|
||||
Data,
|
||||
}
|
||||
|
||||
pub struct FordV1Decoder {
|
||||
step: DecoderStep,
|
||||
manchester_state: ManchesterState,
|
||||
preamble_count: u16,
|
||||
decode_data: u64,
|
||||
decode_count_bit: usize,
|
||||
byte_count: usize,
|
||||
raw_bytes: [u8; DATA_BYTES + 1],
|
||||
sync_event_idx: u8,
|
||||
sync_event_count: u8,
|
||||
sync_events: [u8; 8],
|
||||
}
|
||||
|
||||
impl FordV1Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
preamble_count: 0,
|
||||
decode_data: 0,
|
||||
decode_count_bit: 0,
|
||||
byte_count: 0,
|
||||
raw_bytes: [0; DATA_BYTES + 1],
|
||||
sync_event_idx: 0,
|
||||
sync_event_count: 0,
|
||||
sync_events: [0; 8],
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_state(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
self.preamble_count = 0;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.byte_count = 0;
|
||||
self.raw_bytes = [0; DATA_BYTES + 1];
|
||||
self.sync_event_idx = 0;
|
||||
self.sync_event_count = 0;
|
||||
self.sync_events = [0; 8];
|
||||
}
|
||||
|
||||
/// Short/long matchers. Data and sync use te_delta (39); preamble-long uses DELTA_LONG (40).
|
||||
fn is_short(d: u32) -> bool {
|
||||
duration_diff!(d, TE_SHORT) < TE_DELTA
|
||||
}
|
||||
fn is_long(d: u32) -> bool {
|
||||
duration_diff!(d, TE_LONG) < TE_DELTA
|
||||
}
|
||||
fn is_preamble_long(d: u32) -> bool {
|
||||
duration_diff!(d, TE_LONG) < DELTA_LONG
|
||||
}
|
||||
|
||||
/// Flipper differential-Manchester transition table (same as Ford V0/V2/Kia V7).
|
||||
/// Event: 0=ShortLow,1=ShortHigh,2=LongLow,3=LongHigh. Returns Some(bit) when a bit emits.
|
||||
fn manchester_advance(&mut self, event: u8) -> Option<bool> {
|
||||
let (new_state, emit) = match (self.manchester_state, event) {
|
||||
(ManchesterState::Mid0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 1) => (ManchesterState::Start1, true),
|
||||
(ManchesterState::Mid0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 3) => (ManchesterState::Mid1, true),
|
||||
|
||||
(ManchesterState::Mid1, 0) => (ManchesterState::Start0, true),
|
||||
(ManchesterState::Mid1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Mid1, 2) => (ManchesterState::Mid0, true),
|
||||
(ManchesterState::Mid1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 1) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start1, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Start1, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
_ => (ManchesterState::Mid1, false),
|
||||
};
|
||||
self.manchester_state = new_state;
|
||||
if emit { Some((event & 1) == 1) } else { None }
|
||||
}
|
||||
|
||||
/// Push a decoded Manchester bit into the byte buffer (matches C feed/data byte assembly).
|
||||
fn push_bit(&mut self, data_bit: bool) {
|
||||
self.decode_data = (self.decode_data << 1) | (data_bit as u64);
|
||||
self.decode_count_bit += 1;
|
||||
if self.decode_count_bit & 7 == 0 {
|
||||
let byte_val = (self.decode_data & 0xFF) as u8;
|
||||
if self.byte_count < DATA_BYTES {
|
||||
self.raw_bytes[self.byte_count] = byte_val;
|
||||
self.byte_count += 1;
|
||||
}
|
||||
self.decode_data = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// CRC16/CCITT (poly 0x1021, init 0x0000) — module-local, matches
|
||||
// subghz_protocol_blocks_crc16(data, len, 0x1021, 0x0000).
|
||||
// =========================================================================
|
||||
fn crc16(data: &[u8]) -> u16 {
|
||||
let mut crc: u16 = 0x0000;
|
||||
for &byte in data {
|
||||
crc ^= (byte as u16) << 8;
|
||||
for _ in 0..8 {
|
||||
if crc & 0x8000 != 0 {
|
||||
crc = (crc << 1) ^ 0x1021;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
crc
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Descrambling cipher (ford_v1_decode_with_flag) — operates on the 9-byte
|
||||
// air block in place. Matches the C exactly.
|
||||
// =========================================================================
|
||||
fn decode_with_flag(raw: &mut [u8; 9], flag_byte: u8) {
|
||||
if flag_byte != 0 {
|
||||
let xor_byte = raw[7];
|
||||
for i in 1..7 {
|
||||
raw[i] ^= xor_byte;
|
||||
}
|
||||
} else {
|
||||
let xor_byte = raw[6];
|
||||
for i in 1..6 {
|
||||
raw[i] ^= xor_byte;
|
||||
}
|
||||
raw[7] ^= xor_byte;
|
||||
}
|
||||
|
||||
let b6 = raw[6];
|
||||
let b7 = raw[7];
|
||||
raw[6] = (b6 & 0xAA) | (b7 & 0x55);
|
||||
raw[7] = (b7 & 0xAA) | (b6 & 0x55);
|
||||
}
|
||||
|
||||
/// Parity-driven descramble used when neither strict branch matches (ford_v1_decode).
|
||||
fn decode_air(raw: &mut [u8; 9]) {
|
||||
let endbyte = raw[8];
|
||||
let parity_any = endbyte != 0;
|
||||
let mut parity = 0u8;
|
||||
let mut tmp = endbyte;
|
||||
while tmp != 0 {
|
||||
parity ^= tmp & 1;
|
||||
tmp >>= 1;
|
||||
}
|
||||
let flag_byte = if parity_any { parity } else { 0 };
|
||||
Self::decode_with_flag(raw, flag_byte);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Inverse cipher (encoder side) — ford_v1_encode_inverse_block. Takes a
|
||||
// 9-byte plaintext block and produces the air (scrambled) block in place.
|
||||
// =========================================================================
|
||||
fn encode_inverse_block(block: &mut [u8; 9]) {
|
||||
let mut sum: u8 = 0;
|
||||
for i in 1..=7 {
|
||||
sum = sum.wrapping_add(block[i]);
|
||||
}
|
||||
|
||||
let p6 = block[6];
|
||||
let p7 = block[7];
|
||||
let post6 = (p6 & 0xAA) | (p7 & 0x55);
|
||||
let post7 = (p7 & 0xAA) | (p6 & 0x55);
|
||||
let xorv = post6 ^ post7;
|
||||
|
||||
let xor_byte;
|
||||
if (sum.count_ones() & 1) != 0 {
|
||||
block[6] = xorv;
|
||||
block[7] = post7;
|
||||
xor_byte = post7;
|
||||
} else {
|
||||
block[6] = post6;
|
||||
block[7] = xorv;
|
||||
xor_byte = post6;
|
||||
}
|
||||
|
||||
for i in 1..=5 {
|
||||
block[i] ^= xor_byte;
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_air_9bytes(plain9: &[u8; 9]) -> [u8; 9] {
|
||||
let mut block = *plain9;
|
||||
Self::encode_inverse_block(&mut block);
|
||||
block
|
||||
}
|
||||
|
||||
/// Recover plaintext from an air block by trying both descramble flags and verifying
|
||||
/// the result re-encodes to the same air bytes (ford_v1_plain_from_air).
|
||||
fn plain_from_air(air9: &[u8; 9]) -> Option<[u8; 9]> {
|
||||
for flag in 0u8..2 {
|
||||
let mut cand = *air9;
|
||||
Self::decode_with_flag(&mut cand, flag);
|
||||
let reair = Self::encode_air_9bytes(&cand);
|
||||
if reair == *air9 {
|
||||
return Some(cand);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract serial/button/counter from a plaintext 9-byte block (ford_v1_fields_from_plain).
|
||||
fn fields_from_plain(plain9: &[u8; 9]) -> (u32, u8, u32) {
|
||||
let serial = ((plain9[1] as u32) << 24)
|
||||
| ((plain9[2] as u32) << 16)
|
||||
| ((plain9[3] as u32) << 8)
|
||||
| (plain9[0] as u32);
|
||||
let btn = (plain9[5] >> 4) & 0x0F;
|
||||
let cnt = (((plain9[5] & 0x0F) as u32) << 16) | ((plain9[6] as u32) << 8) | (plain9[7] as u32);
|
||||
(serial, btn, cnt)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// process_data (ford_v1_process_data): CRC16 gate (+ 17-byte inverted
|
||||
// fallback), descramble, field extraction, dual-branch classification.
|
||||
// Returns Some(DecodedSignal) when CRC passes.
|
||||
// =========================================================================
|
||||
fn process_data(&self) -> Option<DecodedSignal> {
|
||||
let mut raw = [0u8; DATA_BYTES];
|
||||
raw.copy_from_slice(&self.raw_bytes[..DATA_BYTES]);
|
||||
|
||||
let mut calc_crc = Self::crc16(&raw[3..15]);
|
||||
let mut recv_crc = ((raw[15] as u16) << 8) | raw[16] as u16;
|
||||
|
||||
// Fallback: bit-invert all 17 bytes and retry the CRC (matches C).
|
||||
if recv_crc != calc_crc {
|
||||
for (i, b) in raw.iter_mut().enumerate() {
|
||||
*b = !self.raw_bytes[i];
|
||||
}
|
||||
calc_crc = Self::crc16(&raw[3..15]);
|
||||
recv_crc = ((raw[15] as u16) << 8) | raw[16] as u16;
|
||||
}
|
||||
|
||||
if recv_crc != calc_crc {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Air block = raw[6..15] (9 bytes). Try both descramble branches "strictly".
|
||||
let mut air9 = [0u8; 9];
|
||||
air9.copy_from_slice(&raw[6..15]);
|
||||
|
||||
let mut decoded_b0 = air9;
|
||||
Self::decode_with_flag(&mut decoded_b0, 0);
|
||||
let mut decoded_b1 = air9;
|
||||
Self::decode_with_flag(&mut decoded_b1, 1);
|
||||
|
||||
let (decoded, strict_ok): ([u8; 9], bool) =
|
||||
if decoded_b0[3] == raw[5] && decoded_b0[4] == raw[6] {
|
||||
(decoded_b0, true)
|
||||
} else if decoded_b1[3] == raw[5] && decoded_b1[4] == raw[6] {
|
||||
(decoded_b1, true)
|
||||
} else if let Some(p) = Self::plain_from_air(&air9) {
|
||||
// Encrypted/rolling: round-trip recovered but not a strict cleartext match.
|
||||
(p, false)
|
||||
} else {
|
||||
let mut p = air9;
|
||||
Self::decode_air(&mut p);
|
||||
(p, false)
|
||||
};
|
||||
|
||||
let recalc_crc = Self::crc16(&raw[3..15]);
|
||||
|
||||
// key1 = raw[0..7] (56 bits, big-endian) → DecodedSignal.data.
|
||||
let mut key1: u64 = 0;
|
||||
for &b in raw.iter().take(7) {
|
||||
key1 = (key1 << 8) | b as u64;
|
||||
}
|
||||
|
||||
let (serial, button, counter) = if strict_ok {
|
||||
let (s, b, c) = Self::fields_from_plain(&decoded);
|
||||
(s, b, c)
|
||||
} else {
|
||||
// Header-only: device id from raw[3..7], no button/counter (matches C).
|
||||
let device_id = ((raw[3] as u32) << 24)
|
||||
| ((raw[4] as u32) << 16)
|
||||
| ((raw[5] as u32) << 8)
|
||||
| (raw[6] as u32);
|
||||
(device_id, 0u8, 0u32)
|
||||
};
|
||||
|
||||
// Stash everything the encoder needs to rebuild the full 17-byte frame from fields.
|
||||
// `data` already carries key1 = raw[0..7] (56 bits). The air block raw[6..15] and the
|
||||
// CRC bytes are regenerated by re-encoding the plaintext, which is fully determined by
|
||||
// serial/button/counter EXCEPT plain[4] (the byte the strict branch constrains, equal to
|
||||
// air9[0]) and the derived checksum plain[8]. So we stash plain[4] plus the strict flag.
|
||||
// Layout (u64, MSB first):
|
||||
// [63:48]=crc16 [47:40]=strict_ok [39:32]=plain[4] [31:0]=0 (reserved).
|
||||
let plain4 = decoded[4];
|
||||
let extra = ((recalc_crc as u64) << 48)
|
||||
| ((strict_ok as u64) << 40)
|
||||
| ((plain4 as u64) << 32);
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter as u16),
|
||||
crc_valid: true,
|
||||
data: key1,
|
||||
data_count_bit: DATA_BITS,
|
||||
encoder_capable: true,
|
||||
extra: Some(extra),
|
||||
protocol_display_name: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Map KAT's generic button command to a Ford V1 4-bit button code
|
||||
/// (Sync=0, Lock=1, Unlock=2, Trunk=4, Panic=8 — matches ford_v1_get_button_name).
|
||||
fn map_button(button: u8) -> u8 {
|
||||
match button {
|
||||
0x01 => 0x01, // Lock
|
||||
0x02 => 0x02, // Unlock
|
||||
0x04 => 0x04, // Trunk
|
||||
0x08 => 0x08, // Panic
|
||||
b => b & 0x0F,
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply serial/button/counter onto a plaintext 9-byte block (ford_v1_plain_apply_fields).
|
||||
fn plain_apply_fields(plain9: &mut [u8; 9], serial: u32, btn: u8, cnt: u32) {
|
||||
let chk = plain9[8]
|
||||
.wrapping_sub(plain9[6])
|
||||
.wrapping_sub(plain9[7])
|
||||
.wrapping_sub(plain9[5]);
|
||||
plain9[0] = (serial & 0xFF) as u8;
|
||||
plain9[1] = ((serial >> 24) & 0xFF) as u8;
|
||||
plain9[2] = ((serial >> 16) & 0xFF) as u8;
|
||||
plain9[3] = ((serial >> 8) & 0xFF) as u8;
|
||||
plain9[5] = (((btn & 0x0F) << 4) | (((cnt >> 16) & 0x0F) as u8)) as u8;
|
||||
plain9[6] = ((cnt >> 8) & 0xFF) as u8;
|
||||
plain9[7] = (cnt & 0xFF) as u8;
|
||||
plain9[8] = chk
|
||||
.wrapping_add(plain9[7])
|
||||
.wrapping_add(plain9[6])
|
||||
.wrapping_add(plain9[5]);
|
||||
}
|
||||
|
||||
/// Rebuild the air block (raw[6..15]) and CRC16 (raw[15..17]) from a plaintext block
|
||||
/// (ford_v1_encoder_rebuild_raw_from_plain).
|
||||
fn rebuild_raw_from_plain(raw17: &mut [u8; DATA_BYTES], plain9: &[u8; 9]) {
|
||||
let air9 = Self::encode_air_9bytes(plain9);
|
||||
raw17[6..15].copy_from_slice(&air9);
|
||||
let c = Self::crc16(&raw17[3..15]);
|
||||
raw17[15] = (c >> 8) as u8;
|
||||
raw17[16] = (c & 0xFF) as u8;
|
||||
}
|
||||
|
||||
fn enc_add_level(signal: &mut Vec<LevelDuration>, level: bool, duration: u32) {
|
||||
if let Some(last) = signal.last_mut() {
|
||||
if last.level == level {
|
||||
*last = LevelDuration::new(level, last.duration_us + duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
signal.push(LevelDuration::new(level, duration));
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for FordV1Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Ford V1"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: DATA_BITS,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[315_000_000, 433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.reset_state();
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
// C: !level && long → Preamble; seed preamble_count = 1.
|
||||
DecoderStep::Reset => {
|
||||
if !level && Self::is_preamble_long(duration) {
|
||||
self.step = DecoderStep::Preamble;
|
||||
self.preamble_count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Preamble => {
|
||||
if Self::is_preamble_long(duration) {
|
||||
self.preamble_count = self.preamble_count.saturating_add(1);
|
||||
} else if Self::is_short(duration) {
|
||||
if self.preamble_count >= PREAMBLE_MIN {
|
||||
// Enter Sync: buffer the first short event.
|
||||
self.sync_event_idx = 0;
|
||||
self.sync_event_count = 1;
|
||||
self.sync_events[0] = if level { EV_SHORT_HIGH } else { EV_SHORT_LOW };
|
||||
self.step = DecoderStep::Sync;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else if self.preamble_count < PREAMBLE_MIN {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
// else: stay in Preamble (long preamble already satisfied, ignore stray pulse)
|
||||
}
|
||||
|
||||
DecoderStep::Sync => {
|
||||
let (ev, is_short) = if Self::is_short(duration) {
|
||||
(if level { EV_SHORT_HIGH } else { EV_SHORT_LOW }, true)
|
||||
} else if Self::is_long(duration) {
|
||||
(if level { EV_LONG_HIGH } else { EV_LONG_LOW }, false)
|
||||
} else {
|
||||
self.step = DecoderStep::Preamble;
|
||||
return None;
|
||||
};
|
||||
|
||||
self.sync_event_idx += 1;
|
||||
if is_short {
|
||||
self.sync_event_count += 1;
|
||||
}
|
||||
if (self.sync_event_idx as usize) < 8 {
|
||||
self.sync_events[self.sync_event_idx as usize] = ev;
|
||||
}
|
||||
|
||||
if self.sync_event_count > 2 {
|
||||
// Sync detected: reset the bit buffer and replay buffered events into Manchester.
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.byte_count = 0;
|
||||
self.raw_bytes = [0; DATA_BYTES + 1];
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
if self.sync_events[0] == EV_SHORT_LOW {
|
||||
self.manchester_state = ManchesterState::Mid0;
|
||||
}
|
||||
self.step = DecoderStep::Data;
|
||||
|
||||
let last = self.sync_event_idx.min(7);
|
||||
for i in 0..=last {
|
||||
let event = self.sync_events[i as usize];
|
||||
if let Some(data_bit) = self.manchester_advance(event) {
|
||||
self.push_bit(data_bit);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.sync_event_idx >= 7 {
|
||||
self.step = DecoderStep::Preamble;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Data => {
|
||||
let event = if Self::is_short(duration) {
|
||||
if level { EV_SHORT_HIGH } else { EV_SHORT_LOW }
|
||||
} else if Self::is_long(duration) {
|
||||
if level { EV_LONG_HIGH } else { EV_LONG_LOW }
|
||||
} else {
|
||||
// Idle gap / odd pulse. The C decoder attempts partial-last-byte variants only
|
||||
// when byte_count==16 and 1-2 bits short; we require all 17 bytes, so any
|
||||
// non-short/long pulse (including very long inter-burst gaps, dur >=
|
||||
// te_long*SILENCE_LONG_MULT) ends the attempt and resets so the next burst can
|
||||
// re-sync from Reset.
|
||||
let _ = duration >= TE_LONG * SILENCE_LONG_MULT;
|
||||
self.reset_state();
|
||||
return None;
|
||||
};
|
||||
|
||||
if let Some(data_bit) = self.manchester_advance(event) {
|
||||
self.push_bit(data_bit);
|
||||
|
||||
if self.byte_count > 16 {
|
||||
let result = self.process_data();
|
||||
self.reset_state();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let serial = decoded.serial?;
|
||||
let counter = decoded.counter.unwrap_or(0) as u32 & 0xF_FFFF;
|
||||
let btn = Self::map_button(button) & 0x0F;
|
||||
|
||||
// We can faithfully re-encode only when the original frame's plaintext was recovered at
|
||||
// decode time (strict branch). `extra` carries: [63:48]=crc16, [47:40]=strict_ok,
|
||||
// [39:32]=plain[4] (the byte the strict branch constrains).
|
||||
let extra = decoded.extra?;
|
||||
let strict_ok = ((extra >> 40) & 0x01) != 0;
|
||||
if !strict_ok {
|
||||
return None;
|
||||
}
|
||||
let plain4 = ((extra >> 32) & 0xFF) as u8;
|
||||
|
||||
// raw[0..7] = key1 (56 bits), taken from `data` (low 56 bits). The decoder packs key1 as
|
||||
// raw[0]<<48 | … | raw[6], so the top 7 bytes of the 56-bit value are raw[0..7].
|
||||
let mut raw17 = [0u8; DATA_BYTES];
|
||||
for (i, b) in raw17.iter_mut().take(7).enumerate() {
|
||||
*b = (decoded.data >> (48 - i * 8)) as u8;
|
||||
}
|
||||
|
||||
// Reconstruct the plaintext block from the decoded fields plus the stashed plain[4].
|
||||
// plain_apply_fields fills [0,1,2,3,5,6,7,8]; plain[4] is preserved from `extra`. The
|
||||
// strict-branch invariant the C decoder verified is plain[4]==air9[0]==raw[6] and
|
||||
// plain[3]==raw[5]; re-encoding plain reproduces the air block and hence those bytes.
|
||||
let mut plain9 = [0u8; 9];
|
||||
plain9[4] = plain4;
|
||||
// Seed checksum-bearing fields with the *decoded* values so the running checksum delta in
|
||||
// plain_apply_fields starts from the original plaintext's relationship, then apply the new
|
||||
// button/counter (same counter; no increment, matching Ford V0's replay policy).
|
||||
plain9[5] = (((decoded.button.unwrap_or(0) & 0x0F) << 4)
|
||||
| (((counter >> 16) & 0x0F) as u8)) as u8;
|
||||
plain9[6] = ((counter >> 8) & 0xFF) as u8;
|
||||
plain9[7] = (counter & 0xFF) as u8;
|
||||
plain9[8] = plain9[5].wrapping_add(plain9[6]).wrapping_add(plain9[7]);
|
||||
Self::plain_apply_fields(&mut plain9, serial, btn, counter);
|
||||
|
||||
// Rebuild raw[5] = plain[3] (strict invariant) and the air block + CRC from plaintext.
|
||||
raw17[5] = plain9[3];
|
||||
Self::rebuild_raw_from_plain(&mut raw17, &plain9);
|
||||
|
||||
// Build the 6-burst upload.
|
||||
let mut signal =
|
||||
Vec::with_capacity(ENC_BURST_COUNT * (ENC_PREAMBLE_PAIRS * 2 + 2 + DATA_BYTES * 16 + 1));
|
||||
for burst in 0..ENC_BURST_COUNT {
|
||||
let mut pkt = raw17;
|
||||
pkt[4] = ENC_BURST_PKT4[burst];
|
||||
let crcw = Self::crc16(&pkt[3..15]);
|
||||
pkt[15] = (crcw >> 8) as u8;
|
||||
pkt[16] = (crcw & 0xFF) as u8;
|
||||
|
||||
// Preamble: 400 pairs of long high / long low.
|
||||
for _ in 0..ENC_PREAMBLE_PAIRS {
|
||||
Self::enc_add_level(&mut signal, true, ENC_SYNC_LONG_US);
|
||||
Self::enc_add_level(&mut signal, false, ENC_SYNC_LONG_US);
|
||||
}
|
||||
// Sync: long high + short low.
|
||||
Self::enc_add_level(&mut signal, true, ENC_SYNC_LONG_US);
|
||||
Self::enc_add_level(&mut signal, false, ENC_SYNC_SHORT_US);
|
||||
|
||||
// Data: each bit → (bit, short) then (!bit, short) — Manchester, MSB first.
|
||||
for &b in pkt.iter() {
|
||||
for bit_i in (0..8).rev() {
|
||||
let bit = ((b >> bit_i) & 1) != 0;
|
||||
Self::enc_add_level(&mut signal, bit, ENC_SYNC_SHORT_US);
|
||||
Self::enc_add_level(&mut signal, !bit, ENC_SYNC_SHORT_US);
|
||||
}
|
||||
}
|
||||
|
||||
// Trailing gap (long for repeats, short for the final burst).
|
||||
let gap = if burst + 1 == ENC_BURST_COUNT {
|
||||
ENC_GAP_LAST_US
|
||||
} else {
|
||||
ENC_GAP_REPEAT_US
|
||||
};
|
||||
Self::enc_add_level(&mut signal, false, gap);
|
||||
}
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FordV1Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// CRC16/CCITT known vector. "123456789" → 0x31C3 for poly 0x1021, init 0x0000
|
||||
/// (the standard CRC-16/XMODEM check value).
|
||||
#[test]
|
||||
fn crc16_known_vector() {
|
||||
let v = FordV1Decoder::crc16(b"123456789");
|
||||
assert_eq!(v, 0x31C3, "CRC-16/XMODEM check value mismatch: got {:04X}", v);
|
||||
}
|
||||
|
||||
/// The descramble cipher must be invertible: encode_inverse_block ∘ decode_with_flag(flag)
|
||||
/// should round-trip for the flag the parity selects.
|
||||
#[test]
|
||||
fn descramble_roundtrip() {
|
||||
// A plaintext block; encode to air, then plain_from_air must recover it exactly.
|
||||
let plain: [u8; 9] = [0x11, 0x22, 0x33, 0x44, 0x55, 0x26, 0x77, 0x88, 0x99];
|
||||
let air = FordV1Decoder::encode_air_9bytes(&plain);
|
||||
let recovered = FordV1Decoder::plain_from_air(&air).expect("round-trip recover");
|
||||
assert_eq!(recovered, plain, "plain_from_air did not recover plaintext");
|
||||
}
|
||||
|
||||
/// Build a canonical Ford V1 plaintext (the strict-branch invariant is plain[4]==plain[0],
|
||||
/// since the cipher leaves byte 0 untouched, so air9[0]==plain[0]). Returns the 17-byte frame.
|
||||
fn build_frame(serial: u32, button: u8, counter: u32) -> [u8; DATA_BYTES] {
|
||||
let mut plain = [0u8; 9];
|
||||
plain[4] = (serial & 0xFF) as u8; // strict: plain[4] == air9[0] == plain[0]
|
||||
FordV1Decoder::plain_apply_fields(&mut plain, serial, button, counter);
|
||||
|
||||
let mut raw17 = [0u8; DATA_BYTES];
|
||||
// key1 head: raw[0..5] arbitrary-ish, raw[5] must equal plain[3] (strict: decoded[3]==raw[5]).
|
||||
raw17[0] = 0xC0;
|
||||
raw17[1] = 0xFF;
|
||||
raw17[2] = 0xEE;
|
||||
raw17[3] = (serial >> 24) as u8;
|
||||
raw17[4] = (serial >> 16) as u8;
|
||||
raw17[5] = plain[3];
|
||||
FordV1Decoder::rebuild_raw_from_plain(&mut raw17, &plain);
|
||||
raw17
|
||||
}
|
||||
|
||||
/// process_data must accept a canonical frame, take the strict branch, and recover fields.
|
||||
#[test]
|
||||
fn process_data_strict_branch() {
|
||||
let (serial, button, counter) = (0x1A2B3C4Du32, 0x02u8, 0x0123u32);
|
||||
let raw17 = build_frame(serial, button, counter);
|
||||
|
||||
let mut dec = FordV1Decoder::new();
|
||||
dec.raw_bytes[..DATA_BYTES].copy_from_slice(&raw17);
|
||||
dec.byte_count = DATA_BYTES;
|
||||
let d = dec.process_data().expect("process_data should accept the frame");
|
||||
assert!(d.crc_valid, "CRC must be valid");
|
||||
assert_eq!(d.serial, Some(serial), "serial mismatch");
|
||||
assert_eq!(d.button, Some(button), "button mismatch");
|
||||
assert_eq!(d.counter, Some(counter as u16), "counter mismatch");
|
||||
assert_eq!(d.data_count_bit, DATA_BITS);
|
||||
}
|
||||
|
||||
/// Reconstruct the 17 on-air data bytes of the first burst from the encoder's Manchester
|
||||
/// upload. The encoder emits, per data bit, (bit,short) then (!bit,short); add_level merges
|
||||
/// adjacent same-level pulses, so a same-bit boundary becomes a long pulse. We re-pair the
|
||||
/// stream by walking half-cells of `short` width (splitting merged long pulses into two halves)
|
||||
/// and reading each bit as the level of its first half-cell.
|
||||
fn first_burst_bytes(upload: &[LevelDuration]) -> [u8; DATA_BYTES] {
|
||||
// Expand merged pulses into ENC_SYNC_SHORT_US half-cells (long = 2 halves).
|
||||
let mut halves: Vec<bool> = Vec::new();
|
||||
for ld in upload {
|
||||
let n = ((ld.duration_us + ENC_SYNC_SHORT_US / 2) / ENC_SYNC_SHORT_US).max(1);
|
||||
for _ in 0..n {
|
||||
halves.push(ld.level);
|
||||
}
|
||||
}
|
||||
// Skip the preamble (400 long pairs = 1600 halves) + sync (long high=2 + short low=1 = 3).
|
||||
let data_start = ENC_PREAMBLE_PAIRS * 4 + 3;
|
||||
let mut out = [0u8; DATA_BYTES];
|
||||
for (byte_i, b) in out.iter_mut().enumerate() {
|
||||
for bit_pos in 0..8 {
|
||||
// Each bit = two half-cells; the bit value is the first half-cell's level.
|
||||
let idx = data_start + (byte_i * 8 + bit_pos) * 2;
|
||||
let bit = halves.get(idx).copied().unwrap_or(false);
|
||||
*b = (*b << 1) | (bit as u8);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Full encode→decode round trip at the on-air-frame level. Decode a canonical frame, re-encode
|
||||
/// it via `encode()`, recover the transmitted 17-byte frame from the first burst, and verify it
|
||||
/// decodes (via process_data) back to the same serial/button/counter with a valid CRC.
|
||||
#[test]
|
||||
fn encode_decode_roundtrip() {
|
||||
let (serial, button, counter) = (0x1A2B3C4Du32, 0x02u8, 0x0123u32);
|
||||
let raw17 = build_frame(serial, button, counter);
|
||||
|
||||
let mut dec = FordV1Decoder::new();
|
||||
dec.raw_bytes[..DATA_BYTES].copy_from_slice(&raw17);
|
||||
dec.byte_count = DATA_BYTES;
|
||||
let decoded = dec.process_data().expect("decode canonical frame");
|
||||
|
||||
// Re-encode (same button) → Manchester upload (6 bursts).
|
||||
let upload = FordV1Decoder::new()
|
||||
.encode(&decoded, button)
|
||||
.expect("encoder should produce an upload");
|
||||
assert!(!upload.is_empty(), "encoder upload must be non-empty");
|
||||
|
||||
// Recover the transmitted frame from burst 0 and decode it through process_data.
|
||||
// Burst 0 overrides pkt[4] = 0x08 (ENC_BURST_PKT4[0]) and recomputes the CRC to match, so
|
||||
// the recovered frame is self-consistent. The strict-branch serial comes from the plaintext
|
||||
// (decoded[1..4],decoded[0]), independent of raw[4], so serial/button/counter still match.
|
||||
let tx = first_burst_bytes(&upload);
|
||||
let mut dec2 = FordV1Decoder::new();
|
||||
dec2.raw_bytes[..DATA_BYTES].copy_from_slice(&tx);
|
||||
dec2.byte_count = DATA_BYTES;
|
||||
let got = dec2
|
||||
.process_data()
|
||||
.expect("re-encoded burst-0 frame must decode via process_data");
|
||||
assert!(got.crc_valid, "round-tripped CRC must be valid");
|
||||
assert_eq!(got.serial, Some(serial), "round-trip serial mismatch");
|
||||
assert_eq!(got.button, Some(button), "round-trip button mismatch");
|
||||
assert_eq!(got.counter, Some(counter as u16), "round-trip counter mismatch");
|
||||
|
||||
// Structural checks: 6 bursts worth of data, each preamble present.
|
||||
let long_highs = upload
|
||||
.iter()
|
||||
.filter(|l| l.level && l.duration_us >= ENC_SYNC_LONG_US)
|
||||
.count();
|
||||
assert!(
|
||||
long_highs >= ENC_BURST_COUNT,
|
||||
"expected at least one long-high preamble pulse per burst"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
//! Ford V2 protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/ford_v2.c` and `ford_v2.h`.
|
||||
//! Manchester 200/400µs (te_delta 260 → threshold ~460µs between short/long), 104 bits (13 bytes), FM.
|
||||
//! Frame begins with a 16-bit Manchester sync that equals ~0x7FA7 (the decoder matches the *inverted*
|
||||
//! shift register against 0x8058); the two sync bytes 0x7F 0xA7 head the 13-byte buffer. Data bits are
|
||||
//! inverted before packing (`data_bit = !data_bit`). Structure is validated by the two sync bytes plus a
|
||||
//! known button code. Encoder supported (matches subghz_protocol_encoder_ford_v2).
|
||||
//!
|
||||
//! Decoder steps: Reset → Preamble (≥64 shorts) → Sync (find 0x7FA7) → Data (11 bytes).
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 200;
|
||||
const TE_LONG: u32 = 400;
|
||||
const TE_DELTA: u32 = 260;
|
||||
const DATA_BITS: usize = 104;
|
||||
const DATA_BYTES: usize = 13;
|
||||
const PREAMBLE_MIN: u16 = 64;
|
||||
const SYNC_0: u8 = 0x7F;
|
||||
const SYNC_1: u8 = 0xA7;
|
||||
const SYNC_BITS: u8 = 16;
|
||||
const INTER_BURST_GAP_US: u32 = 15000;
|
||||
|
||||
// Encoder constants (subghz_protocol_encoder_ford_v2)
|
||||
const ENC_TE_SHORT: u32 = 240;
|
||||
const ENC_PREAMBLE_PAIRS: usize = 70;
|
||||
const ENC_BURST_COUNT: usize = 6;
|
||||
const ENC_INTER_BURST_GAP_US: u32 = 16000;
|
||||
const ENC_SYNC_LO_US: u32 = 476;
|
||||
const TAIL_RAW_BYTES: usize = 5;
|
||||
|
||||
/// Inverted 16-bit sync the decoder matches against (= !0x7FA7).
|
||||
const SYNC_SHIFT16_INV: u16 = !(((SYNC_0 as u16) << 8) | SYNC_1 as u16);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0 = 0,
|
||||
Mid1 = 1,
|
||||
Start0 = 2,
|
||||
Start1 = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Preamble,
|
||||
Sync,
|
||||
Data,
|
||||
}
|
||||
|
||||
pub struct FordV2Decoder {
|
||||
step: DecoderStep,
|
||||
manchester_state: ManchesterState,
|
||||
preamble_count: u16,
|
||||
raw_bytes: [u8; DATA_BYTES],
|
||||
byte_count: usize,
|
||||
decode_data: u16,
|
||||
decode_count_bit: usize,
|
||||
sync_shift: u16,
|
||||
sync_bit_count: u8,
|
||||
}
|
||||
|
||||
impl FordV2Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
preamble_count: 0,
|
||||
raw_bytes: [0; DATA_BYTES],
|
||||
byte_count: 0,
|
||||
decode_data: 0,
|
||||
decode_count_bit: 0,
|
||||
sync_shift: 0,
|
||||
sync_bit_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_state(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
self.preamble_count = 0;
|
||||
self.raw_bytes = [0; DATA_BYTES];
|
||||
self.byte_count = 0;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.sync_shift = 0;
|
||||
self.sync_bit_count = 0;
|
||||
}
|
||||
|
||||
fn is_short(d: u32) -> bool {
|
||||
duration_diff!(d, TE_SHORT) < TE_DELTA
|
||||
}
|
||||
|
||||
fn is_long(d: u32) -> bool {
|
||||
duration_diff!(d, TE_LONG) < TE_DELTA
|
||||
}
|
||||
|
||||
/// Flipper Manchester transition table (same as Ford V0). Event 0=ShortLow,1=ShortHigh,
|
||||
/// 2=LongLow,3=LongHigh. Returns Some(bit) when a bit emits.
|
||||
fn manchester_advance(&mut self, event: u8) -> Option<bool> {
|
||||
let (new_state, emit) = match (self.manchester_state, event) {
|
||||
(ManchesterState::Mid0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 1) => (ManchesterState::Start1, true),
|
||||
(ManchesterState::Mid0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 3) => (ManchesterState::Mid1, true),
|
||||
|
||||
(ManchesterState::Mid1, 0) => (ManchesterState::Start0, true),
|
||||
(ManchesterState::Mid1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Mid1, 2) => (ManchesterState::Mid0, true),
|
||||
(ManchesterState::Mid1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 1) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start1, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Start1, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
_ => (ManchesterState::Mid1, false),
|
||||
};
|
||||
self.manchester_state = new_state;
|
||||
if emit { Some((event & 1) == 1) } else { None }
|
||||
}
|
||||
|
||||
/// Map (level, duration) → Manchester event. Ford V2 polarity: level ? High : Low.
|
||||
fn pulse_event(level: bool, duration: u32) -> Option<u8> {
|
||||
if Self::is_short(duration) {
|
||||
Some(if level { 1 } else { 0 })
|
||||
} else if Self::is_long(duration) {
|
||||
Some(if level { 3 } else { 2 })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn button_is_valid(btn: u8) -> bool {
|
||||
matches!(btn, 0x10 | 0x11 | 0x13 | 0x14 | 0x15)
|
||||
}
|
||||
|
||||
/// Enter the Sync step from the preamble, seeding state for the triggering long-low pulse.
|
||||
fn enter_sync_from_preamble(&mut self, level: bool, duration: u32) {
|
||||
self.step = DecoderStep::Sync;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.byte_count = 0;
|
||||
self.sync_shift = 0;
|
||||
self.sync_bit_count = 0;
|
||||
self.raw_bytes = [0; DATA_BYTES];
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
|
||||
if let Some(event) = Self::pulse_event(level, duration) {
|
||||
// Low event (0/2) seeds Mid0 (matches the C `if(ev==ShortLow||ev==LongLow) state=Mid0`).
|
||||
if event == 0 || event == 2 {
|
||||
self.manchester_state = ManchesterState::Mid0;
|
||||
}
|
||||
self.feed_event(event);
|
||||
} else {
|
||||
self.reset_state();
|
||||
}
|
||||
}
|
||||
|
||||
/// Process one Manchester event in Sync or Data step. Returns Some when a frame commits.
|
||||
fn feed_event(&mut self, event: u8) -> Option<DecodedSignal> {
|
||||
if self.step == DecoderStep::Sync {
|
||||
if let Some(bit) = self.manchester_advance(event) {
|
||||
self.sync_shift = (self.sync_shift << 1) | (bit as u16);
|
||||
if self.sync_bit_count < SYNC_BITS {
|
||||
self.sync_bit_count += 1;
|
||||
}
|
||||
if self.sync_bit_count >= SYNC_BITS && self.sync_shift == SYNC_SHIFT16_INV {
|
||||
// Enter data: prime sync bytes and bit counter.
|
||||
self.raw_bytes = [0; DATA_BYTES];
|
||||
self.raw_bytes[0] = SYNC_0;
|
||||
self.raw_bytes[1] = SYNC_1;
|
||||
self.byte_count = 2;
|
||||
self.step = DecoderStep::Data;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = SYNC_BITS as usize;
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
// Data step
|
||||
if let Some(bit) = self.manchester_advance(event) {
|
||||
let data_bit = !bit; // Ford V2 inverts decoded bits
|
||||
self.decode_data = (self.decode_data << 1) | (data_bit as u16);
|
||||
self.decode_count_bit += 1;
|
||||
|
||||
if self.decode_count_bit & 7 == 0 {
|
||||
let byte_val = (self.decode_data & 0xFF) as u8;
|
||||
if self.byte_count < DATA_BYTES {
|
||||
self.raw_bytes[self.byte_count] = byte_val;
|
||||
self.byte_count += 1;
|
||||
}
|
||||
self.decode_data = 0;
|
||||
|
||||
if self.byte_count == DATA_BYTES {
|
||||
let result = self.commit_frame();
|
||||
self.reset_state();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Validate sync bytes + structure and build the decoded signal.
|
||||
fn commit_frame(&self) -> Option<DecodedSignal> {
|
||||
let k = &self.raw_bytes;
|
||||
if k[0] != SYNC_0 || k[1] != SYNC_1 {
|
||||
return None;
|
||||
}
|
||||
if !Self::button_is_valid(k[6]) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let serial = ((k[2] as u32) << 24)
|
||||
| ((k[3] as u32) << 16)
|
||||
| ((k[4] as u32) << 8)
|
||||
| (k[5] as u32);
|
||||
let counter = (((k[7] & 0x7F) as u16) << 9) | ((k[8] as u16) << 1) | ((k[9] >> 7) as u16);
|
||||
|
||||
// Top 8 bytes → 64-bit data for display/export (matches generic.data).
|
||||
let mut data = 0u64;
|
||||
for &byte in k.iter().take(8) {
|
||||
data = (data << 8) | byte as u64;
|
||||
}
|
||||
// Tail raw bytes k[8..13] → extra (40 bits) so the encoder can rebuild the full frame.
|
||||
let mut extra = 0u64;
|
||||
for &byte in k.iter().skip(8).take(TAIL_RAW_BYTES) {
|
||||
extra = (extra << 8) | byte as u64;
|
||||
}
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(k[6]),
|
||||
counter: Some(counter),
|
||||
crc_valid: true, // validated by sync bytes + button structure
|
||||
data,
|
||||
data_count_bit: DATA_BITS,
|
||||
encoder_capable: true,
|
||||
extra: Some(extra),
|
||||
protocol_display_name: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Map KAT's generic button command to a Ford V2 button code.
|
||||
fn map_button(button: u8) -> u8 {
|
||||
match button {
|
||||
0x01 => 0x10, // Lock
|
||||
0x02 => 0x11, // Unlock
|
||||
0x04 => 0x13, // Trunk
|
||||
0x08 => 0x14, // Panic
|
||||
b if Self::button_is_valid(b) => b, // already a Ford V2 code
|
||||
_ => 0x11,
|
||||
}
|
||||
}
|
||||
|
||||
fn parity8(mut v: u8) -> u8 {
|
||||
let mut p = 0u8;
|
||||
while v != 0 {
|
||||
p ^= v & 1;
|
||||
v >>= 1;
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
fn enc_add_level(signal: &mut Vec<LevelDuration>, level: bool, duration: u32) {
|
||||
if let Some(last) = signal.last_mut() {
|
||||
if last.level == level {
|
||||
*last = LevelDuration::new(level, last.duration_us + duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
signal.push(LevelDuration::new(level, duration));
|
||||
}
|
||||
|
||||
fn enc_manchester_bit(signal: &mut Vec<LevelDuration>, bit: bool) {
|
||||
if bit {
|
||||
Self::enc_add_level(signal, true, ENC_TE_SHORT);
|
||||
Self::enc_add_level(signal, false, ENC_TE_SHORT);
|
||||
} else {
|
||||
Self::enc_add_level(signal, false, ENC_TE_SHORT);
|
||||
Self::enc_add_level(signal, true, ENC_TE_SHORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for FordV2Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Ford V2"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: DATA_BITS,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[315_000_000, 433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.reset_state();
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if Self::is_short(duration) {
|
||||
self.preamble_count = 1;
|
||||
self.step = DecoderStep::Preamble;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Preamble => {
|
||||
if Self::is_short(duration) {
|
||||
if self.preamble_count < u16::MAX {
|
||||
self.preamble_count += 1;
|
||||
}
|
||||
} else if !level && Self::is_long(duration) {
|
||||
if self.preamble_count >= PREAMBLE_MIN {
|
||||
self.enter_sync_from_preamble(level, duration);
|
||||
} else {
|
||||
self.reset_state();
|
||||
}
|
||||
} else {
|
||||
self.reset_state();
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Sync | DecoderStep::Data => {
|
||||
if let Some(event) = Self::pulse_event(level, duration) {
|
||||
if let Some(result) = self.feed_event(event) {
|
||||
return Some(result);
|
||||
}
|
||||
} else {
|
||||
// Non-short/long pulse (gap/out-of-range) ends the attempt.
|
||||
let _ = duration >= INTER_BURST_GAP_US;
|
||||
self.reset_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
// Rebuild the 13-byte frame: bytes 0..8 from data, tail bytes 8..13 from extra.
|
||||
let mut raw = [0u8; DATA_BYTES];
|
||||
for (i, b) in raw.iter_mut().take(8).enumerate() {
|
||||
*b = (decoded.data >> (56 - i * 8)) as u8;
|
||||
}
|
||||
let extra = decoded.extra.unwrap_or(0);
|
||||
for (i, b) in raw.iter_mut().skip(8).take(TAIL_RAW_BYTES).enumerate() {
|
||||
*b = (extra >> (32 - i * 8)) as u8;
|
||||
}
|
||||
|
||||
// Apply the requested button and refresh the byte-7 parity MSB.
|
||||
raw[6] = Self::map_button(button);
|
||||
if !Self::button_is_valid(raw[6]) {
|
||||
return None;
|
||||
}
|
||||
let parity_msb = Self::parity8(raw[6]) << 7;
|
||||
raw[7] = (raw[7] & 0x7F) | parity_msb;
|
||||
// Ensure sync header is present.
|
||||
raw[0] = SYNC_0;
|
||||
raw[1] = SYNC_1;
|
||||
|
||||
let mut signal = Vec::with_capacity(ENC_BURST_COUNT * (ENC_PREAMBLE_PAIRS * 2 + DATA_BITS * 2 + 4));
|
||||
for burst in 0..ENC_BURST_COUNT {
|
||||
// Preamble: 70 pairs of (low short, high short)
|
||||
for _ in 0..ENC_PREAMBLE_PAIRS {
|
||||
Self::enc_add_level(&mut signal, false, ENC_TE_SHORT);
|
||||
Self::enc_add_level(&mut signal, true, ENC_TE_SHORT);
|
||||
}
|
||||
// Sync low + high short
|
||||
Self::enc_add_level(&mut signal, false, ENC_SYNC_LO_US);
|
||||
Self::enc_add_level(&mut signal, true, ENC_TE_SHORT);
|
||||
// Data bits 1..103 (bit 0 implied by the high short above)
|
||||
for bit_pos in 1..DATA_BITS {
|
||||
let byte_idx = bit_pos / 8;
|
||||
let bit_idx = 7 - (bit_pos % 8);
|
||||
let bit = (raw[byte_idx] >> bit_idx) & 1 != 0;
|
||||
Self::enc_manchester_bit(&mut signal, bit);
|
||||
}
|
||||
if burst + 1 < ENC_BURST_COUNT {
|
||||
Self::enc_add_level(&mut signal, true, ENC_INTER_BURST_GAP_US);
|
||||
}
|
||||
}
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FordV2Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
//! Ford V3 protocol decoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/ford_v3.c` and `ford_v3.h`.
|
||||
//! Manchester 240/480µs, 104 bits (13 bytes), FM, plaintext (no CRC/encryption). Decode-only —
|
||||
//! the reference encoder is NULL.
|
||||
//!
|
||||
//! Manchester uses Flipper's manchester_decoder.h transition table (same as Ford V0), but Ford V3
|
||||
//! maps level the OPPOSITE way from Ford V0: `level ? ShortHigh/LongHigh : ShortLow/LongLow`
|
||||
//! (Ford V0 uses `level ? Low : High`). Decoder steps: Reset → Preamble (≥30 shorts) → Data.
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 240;
|
||||
const TE_LONG: u32 = 480;
|
||||
const TE_DELTA: u32 = 60;
|
||||
const DATA_BITS: usize = 104;
|
||||
const DATA_BYTES: usize = 13;
|
||||
const PREAMBLE_MIN: u16 = 30;
|
||||
|
||||
const BTN_LOCK: u8 = 0x01;
|
||||
const BTN_UNLOCK: u8 = 0x02;
|
||||
|
||||
/// Manchester state machine (Flipper manchester_decoder.h transition table; same as Ford V0).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0 = 0,
|
||||
Mid1 = 1,
|
||||
Start0 = 2,
|
||||
Start1 = 3,
|
||||
}
|
||||
|
||||
/// Decoder step states (matches FordV3DecoderStep in ford_v3.c)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Preamble,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// Ford V3 protocol decoder (matches SubGhzProtocolDecoderFordV3)
|
||||
pub struct FordV3Decoder {
|
||||
step: DecoderStep,
|
||||
manchester_state: ManchesterState,
|
||||
raw_bytes: [u8; DATA_BYTES],
|
||||
bit_count: usize,
|
||||
preamble_count: u16,
|
||||
}
|
||||
|
||||
impl FordV3Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
raw_bytes: [0; DATA_BYTES],
|
||||
bit_count: 0,
|
||||
preamble_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset accumulators (matches ford_v3_reset_data)
|
||||
fn reset_data(&mut self) {
|
||||
self.raw_bytes = [0; DATA_BYTES];
|
||||
self.bit_count = 0;
|
||||
self.preamble_count = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
|
||||
/// Add a decoded bit MSB-first into the byte buffer (matches ford_v3_add_bit)
|
||||
fn add_bit(&mut self, bit: bool) {
|
||||
if self.bit_count >= DATA_BITS {
|
||||
return;
|
||||
}
|
||||
let byte_index = self.bit_count / 8;
|
||||
let bit_in_byte = 7 - (self.bit_count % 8);
|
||||
if bit {
|
||||
self.raw_bytes[byte_index] |= 1 << bit_in_byte;
|
||||
}
|
||||
self.bit_count += 1;
|
||||
}
|
||||
|
||||
/// Manchester state machine (Flipper manchester_advance).
|
||||
/// Event: 0=ShortLow, 1=ShortHigh, 2=LongLow, 3=LongHigh. Returns Some(bit) when a bit emits.
|
||||
fn manchester_advance(&mut self, event: u8) -> Option<bool> {
|
||||
let (new_state, emit) = match (self.manchester_state, event) {
|
||||
(ManchesterState::Mid0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 1) => (ManchesterState::Start1, true),
|
||||
(ManchesterState::Mid0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 3) => (ManchesterState::Mid1, true),
|
||||
|
||||
(ManchesterState::Mid1, 0) => (ManchesterState::Start0, true),
|
||||
(ManchesterState::Mid1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Mid1, 2) => (ManchesterState::Mid0, true),
|
||||
(ManchesterState::Mid1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 1) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start1, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Start1, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
_ => (ManchesterState::Mid1, false),
|
||||
};
|
||||
|
||||
self.manchester_state = new_state;
|
||||
if emit {
|
||||
Some((event & 1) == 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_short(duration: u32) -> bool {
|
||||
duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||
}
|
||||
|
||||
fn is_long(duration: u32) -> bool {
|
||||
duration_diff!(duration, TE_LONG) < TE_DELTA
|
||||
}
|
||||
|
||||
/// Build the decoded signal when 104 bits are collected (matches ford_v3_parse_fields).
|
||||
fn build_signal(&self) -> DecodedSignal {
|
||||
let b = &self.raw_bytes;
|
||||
let serial = ((b[1] as u32) << 24)
|
||||
| ((b[2] as u32) << 16)
|
||||
| ((b[3] as u32) << 8)
|
||||
| (b[4] as u32);
|
||||
// Counter is the bitwise-inverted bytes 7 and 8 (ref: ~b[7], ~b[8])
|
||||
let counter = (((!b[7]) as u16) << 8) | ((!b[8]) as u16);
|
||||
let button = if b[6] & 0x01 != 0 { BTN_UNLOCK } else { BTN_LOCK };
|
||||
|
||||
// Pack the first 8 bytes (big-endian) into the 64-bit data field for display/export
|
||||
// (matches ford_v3.c serialize, which stores bytes[0..8] in generic.data).
|
||||
let mut data = 0u64;
|
||||
for &byte in b.iter().take(8) {
|
||||
data = (data << 8) | byte as u64;
|
||||
}
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid: true, // Ford V3 is plaintext with no CRC
|
||||
data,
|
||||
data_count_bit: DATA_BITS,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for FordV3Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Ford V3"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: DATA_BITS,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[315_000_000, 433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.reset_data();
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
// Ref: any short pulse begins the preamble (no level check).
|
||||
DecoderStep::Reset => {
|
||||
if Self::is_short(duration) {
|
||||
self.reset_data();
|
||||
self.preamble_count = 1;
|
||||
self.step = DecoderStep::Preamble;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Preamble => {
|
||||
if Self::is_short(duration) {
|
||||
self.preamble_count += 1;
|
||||
} else if self.preamble_count >= PREAMBLE_MIN && Self::is_long(duration) {
|
||||
// First data bit: long pulse, mapped level ? LongHigh : LongLow.
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
let event = if level { 3 } else { 2 };
|
||||
if let Some(bit) = self.manchester_advance(event) {
|
||||
self.add_bit(bit);
|
||||
}
|
||||
self.step = DecoderStep::Data;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Data => {
|
||||
let short = Self::is_short(duration);
|
||||
let long = Self::is_long(duration);
|
||||
|
||||
if !short && !long {
|
||||
// Gap / out-of-range pulse ends the frame.
|
||||
let ready = self.bit_count >= DATA_BITS;
|
||||
let result = if ready { Some(self.build_signal()) } else { None };
|
||||
self.step = DecoderStep::Reset;
|
||||
self.reset_data();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Ford V3 polarity: level ? High : Low (opposite of Ford V0).
|
||||
let event = if level {
|
||||
if short { 1 } else { 3 } // ShortHigh / LongHigh
|
||||
} else if short {
|
||||
0 // ShortLow
|
||||
} else {
|
||||
2 // LongLow
|
||||
};
|
||||
|
||||
if let Some(bit) = self.manchester_advance(event) {
|
||||
self.add_bit(bit);
|
||||
if self.bit_count >= DATA_BITS {
|
||||
let result = self.build_signal();
|
||||
self.step = DecoderStep::Reset;
|
||||
self.reset_data();
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FordV3Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
//! Honda Static protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/honda_static.c` and
|
||||
//! `honda_static.h`. Honda/Acura fixed-code keyfobs. FM, 315 MHz + 433.92 MHz, 64-bit frame.
|
||||
//!
|
||||
//! Unlike most KAT decoders, Honda Static does NOT use the Flipper manchester_decoder.h transition
|
||||
//! table. It buffers a per-element *symbol stream* (one bit per ~63µs element) and then performs a
|
||||
//! custom Manchester unpack over symbol pairs (see `honda_static_manchester_pack_64` in the C). A
|
||||
//! short pulse contributes one symbol = the pulse level; a long pulse contributes two symbols of the
|
||||
//! same level. Anything outside both ranges (e.g. the 700µs sync or a trailing gap) terminates the
|
||||
//! buffer and triggers a parse attempt.
|
||||
//!
|
||||
//! Frame (64 bits, MSB-first into 8 bytes): button (4b) | serial (28b) | counter (24b) |
|
||||
//! checksum (8b). The checksum is an XOR of bytes[0..7] (the first 7 bytes). Emission is gated on
|
||||
//! the checksum validating, so Honda Static will not false-match. The parser tries the
|
||||
//! inverted-Manchester interpretation first (which is what the encoder emits), then non-inverted
|
||||
//! forward, then a bit-reversed-bytes pass — matching the C.
|
||||
//!
|
||||
//! The exported `data` (Key) word is the C `generic.data`: a compact nibble-packed layout
|
||||
//! (`honda_static_pack_compact`), NOT the raw decoded packet bytes.
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const BIT_COUNT: usize = 64;
|
||||
const MIN_SYMBOLS: usize = 36;
|
||||
const SHORT_BASE_US: u32 = 28;
|
||||
const SHORT_SPAN_US: u32 = 70;
|
||||
const LONG_BASE_US: u32 = 61;
|
||||
const LONG_SPAN_US: u32 = 130;
|
||||
const SYNC_TIME_US: u32 = 700;
|
||||
const ELEMENT_TIME_US: u32 = 63;
|
||||
const SYMBOL_CAPACITY: usize = 512;
|
||||
const PREAMBLE_ALTERNATING_COUNT: usize = 160;
|
||||
const PREAMBLE_MAX_TRANSITIONS: u16 = 19;
|
||||
|
||||
// Reported timing constants (informational; matches ProtoPirate protocol_items profile).
|
||||
const TE_SHORT: u32 = ELEMENT_TIME_US;
|
||||
const TE_LONG: u32 = SYNC_TIME_US;
|
||||
const TE_DELTA: u32 = 120;
|
||||
|
||||
// KAT generic button codes.
|
||||
const BTN_LOCK: u8 = 0x01;
|
||||
const BTN_UNLOCK: u8 = 0x02;
|
||||
const BTN_TRUNK: u8 = 0x04;
|
||||
const BTN_PANIC: u8 = 0x08;
|
||||
|
||||
// honda_static_encoder_button_map[4] in the C ({0x02,0x04,0x08,0x05}); used by the encoder remap.
|
||||
const ENCODER_BUTTON_MAP: [u8; 4] = [0x02, 0x04, 0x08, 0x05];
|
||||
|
||||
/// Decoded Honda Static fields (matches HondaStaticFields).
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
struct HondaStaticFields {
|
||||
button: u8,
|
||||
serial: u32,
|
||||
counter: u32,
|
||||
/// XOR checksum byte (mirrors the C struct field; validation recomputes it, so this is
|
||||
/// retained for fidelity/inspection rather than being read on the hot path).
|
||||
#[allow(dead_code)]
|
||||
checksum: u8,
|
||||
}
|
||||
|
||||
/// Honda Static decoder (matches SubGhzProtocolDecoderHondaStatic).
|
||||
pub struct HondaStaticDecoder {
|
||||
/// Per-element symbol stream (one bit per ~63µs element). Index 0 is the first received symbol.
|
||||
symbols: Vec<bool>,
|
||||
}
|
||||
|
||||
impl HondaStaticDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
symbols: Vec::with_capacity(SYMBOL_CAPACITY),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract `count` bits starting at `start`, MSB-first across the byte array
|
||||
/// (matches honda_static_get_bits / honda_static_get_bits_u32, shift = (~bit_index)&7).
|
||||
fn get_bits(data: &[u8], start: usize, count: usize) -> u32 {
|
||||
let mut value: u32 = 0;
|
||||
for i in 0..count {
|
||||
let bit_index = start + i;
|
||||
let byte = data[bit_index >> 3];
|
||||
let shift = (!bit_index) & 0x07;
|
||||
value = (value << 1) | (((byte >> shift) & 1) as u32);
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
/// XOR checksum over the first 7 bytes of the 8-byte packet (matches the inline checksum loop in
|
||||
/// honda_static_validate_forward_packet / honda_static_build_packet_bytes).
|
||||
fn packet_checksum(packet: &[u8]) -> u8 {
|
||||
let mut checksum = 0u8;
|
||||
for &b in packet.iter().take(7) {
|
||||
checksum ^= b;
|
||||
}
|
||||
checksum
|
||||
}
|
||||
|
||||
/// Reverse the bit order of a byte (matches pp_reverse_bits8).
|
||||
fn reverse_bits8(value: u8) -> u8 {
|
||||
let mut v = value;
|
||||
v = ((v & 0xF0) >> 4) | ((v & 0x0F) << 4);
|
||||
v = ((v & 0xCC) >> 2) | ((v & 0x33) << 2);
|
||||
v = ((v & 0xAA) >> 1) | ((v & 0x55) << 1);
|
||||
v
|
||||
}
|
||||
|
||||
fn is_valid_button(button: u8) -> bool {
|
||||
// honda_static_is_valid_button: button <= 9 && ((0x336 >> button) & 1)
|
||||
// 0x336 = 0b1100110110 → valid buttons: 1,2,4,5,8,9.
|
||||
button <= 9 && ((0x336u16 >> button) & 1) != 0
|
||||
}
|
||||
|
||||
fn is_valid_serial(serial: u32) -> bool {
|
||||
serial != 0 && serial != 0x0FFF_FFFF
|
||||
}
|
||||
|
||||
/// Pack fields into the 64-bit compact "Key" word (matches honda_static_pack_compact →
|
||||
/// pp_bytes_to_u64_be over the compact[8] nibble-packed layout).
|
||||
fn pack_compact(fields: &HondaStaticFields) -> u64 {
|
||||
let mut compact = [0u8; 8];
|
||||
compact[0] = fields.button & 0x0F;
|
||||
compact[1] = (fields.serial >> 20) as u8;
|
||||
compact[2] = (fields.serial >> 12) as u8;
|
||||
compact[3] = (fields.serial >> 4) as u8;
|
||||
compact[4] = (fields.serial << 4) as u8;
|
||||
compact[5] = (fields.counter >> 16) as u8;
|
||||
compact[6] = (fields.counter >> 8) as u8;
|
||||
compact[7] = fields.counter as u8;
|
||||
u64::from_be_bytes(compact)
|
||||
}
|
||||
|
||||
/// Build the raw 8-byte (64-bit) packet from fields, MSB-first
|
||||
/// (matches honda_static_build_packet_bytes; checksum filled into byte 7).
|
||||
fn build_packet_bytes(fields: &HondaStaticFields) -> [u8; 8] {
|
||||
let mut packet = [0u8; 8];
|
||||
Self::set_bits(&mut packet, 0, 4, (fields.button & 0x0F) as u32);
|
||||
Self::set_bits(&mut packet, 4, 28, fields.serial);
|
||||
Self::set_bits(&mut packet, 32, 24, fields.counter);
|
||||
let checksum = Self::packet_checksum(&packet);
|
||||
Self::set_bits(&mut packet, 56, 8, checksum as u32);
|
||||
packet
|
||||
}
|
||||
|
||||
/// Set `count` bits starting at `start`, MSB-first (matches honda_static_set_bits).
|
||||
fn set_bits(data: &mut [u8], start: usize, count: usize, value: u32) {
|
||||
for i in 0..count {
|
||||
let bit_index = start + i;
|
||||
let byte_index = bit_index >> 3;
|
||||
let shift = (!bit_index) & 0x07;
|
||||
let mask = 1u8 << shift;
|
||||
let bit = ((value >> (count - 1 - i)) & 1) != 0;
|
||||
if bit {
|
||||
data[byte_index] |= mask;
|
||||
} else {
|
||||
data[byte_index] &= !mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate a forward (as-decoded) 8-byte packet (matches honda_static_validate_forward_packet).
|
||||
fn validate_forward_packet(packet: &[u8; 8]) -> Option<HondaStaticFields> {
|
||||
let button = Self::get_bits(packet, 0, 4) as u8;
|
||||
let serial = Self::get_bits(packet, 4, 28);
|
||||
let counter = Self::get_bits(packet, 32, 24);
|
||||
let checksum = Self::get_bits(packet, 56, 8) as u8;
|
||||
let checksum_calc = Self::packet_checksum(packet);
|
||||
|
||||
if checksum != checksum_calc {
|
||||
return None;
|
||||
}
|
||||
if !Self::is_valid_button(button) {
|
||||
return None;
|
||||
}
|
||||
if !Self::is_valid_serial(serial) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(HondaStaticFields {
|
||||
button,
|
||||
serial,
|
||||
counter,
|
||||
checksum,
|
||||
})
|
||||
}
|
||||
|
||||
/// Validate a bit-reversed packet (matches honda_static_validate_reverse_packet). Note: the C
|
||||
/// does NOT re-check the checksum here (it reverses bytes then validates button/serial only),
|
||||
/// so this path is not checksum-gated. We mirror that, but the caller flags crc_valid=false.
|
||||
fn validate_reverse_packet(packet: &[u8; 8]) -> Option<HondaStaticFields> {
|
||||
let mut reversed = [0u8; 8];
|
||||
for (i, b) in packet.iter().enumerate() {
|
||||
reversed[i] = Self::reverse_bits8(*b);
|
||||
}
|
||||
|
||||
let button = Self::get_bits(&reversed, 0, 4) as u8;
|
||||
let serial = Self::get_bits(&reversed, 4, 28);
|
||||
let counter = Self::get_bits(&reversed, 32, 24);
|
||||
let checksum = Self::packet_checksum(&reversed);
|
||||
|
||||
if !Self::is_valid_button(button) {
|
||||
return None;
|
||||
}
|
||||
if !Self::is_valid_serial(serial) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(HondaStaticFields {
|
||||
button,
|
||||
serial,
|
||||
counter,
|
||||
checksum,
|
||||
})
|
||||
}
|
||||
|
||||
/// Manchester unpack over symbol pairs (matches honda_static_manchester_pack_64).
|
||||
/// Returns the packed 8-byte packet plus how many bits were collected. `inverted`:
|
||||
/// bit=1 when (a==0,b==1); non-inverted: bit=1 when (a==1,b==0). Equal adjacent symbols are
|
||||
/// skipped (advance by 1).
|
||||
fn manchester_pack_64(symbols: &[bool], start_pos: usize, inverted: bool) -> ([u8; 8], usize) {
|
||||
let mut packet = [0u8; 8];
|
||||
let count = symbols.len();
|
||||
let mut pos = start_pos;
|
||||
let mut bit_count: usize = 0;
|
||||
|
||||
while pos + 1 < count {
|
||||
if bit_count >= BIT_COUNT {
|
||||
break;
|
||||
}
|
||||
let a = symbols[pos];
|
||||
let b = symbols[pos + 1];
|
||||
if a == b {
|
||||
pos += 1;
|
||||
continue;
|
||||
}
|
||||
let bit = if inverted {
|
||||
!a && b // a==0 && b==1
|
||||
} else {
|
||||
a && !b // a==1 && b==0
|
||||
};
|
||||
if bit {
|
||||
let shift = (!bit_count) & 0x07;
|
||||
packet[bit_count >> 3] |= 1u8 << shift;
|
||||
}
|
||||
bit_count += 1;
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
(packet, bit_count)
|
||||
}
|
||||
|
||||
/// Locate the data start (after the alternating preamble + sync run) and unpack/validate.
|
||||
/// Matches honda_static_parse_symbols. Returns the validated fields and whether the checksum
|
||||
/// path validated it (forward = true; reverse = false).
|
||||
fn parse_symbols(symbols: &[bool], inverted: bool) -> Option<(HondaStaticFields, bool)> {
|
||||
let count = symbols.len();
|
||||
if count == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Walk the alternating preamble: count consecutive transitions; when a non-transition
|
||||
// follows a run longer than PREAMBLE_MAX_TRANSITIONS, that's the preamble/data boundary.
|
||||
let mut index = 1usize;
|
||||
let mut transitions: u16 = 0;
|
||||
while index < count {
|
||||
if symbols[index] != symbols[index - 1] {
|
||||
transitions += 1;
|
||||
} else {
|
||||
if transitions > PREAMBLE_MAX_TRANSITIONS {
|
||||
break;
|
||||
}
|
||||
transitions = 0;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
if index >= count {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Skip forward over the equal-adjacent run (the sync gap).
|
||||
while (index + 1 < count) && (symbols[index] == symbols[index + 1]) {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
let data_start = index;
|
||||
let (packet, bit_count) = Self::manchester_pack_64(symbols, data_start, inverted);
|
||||
if bit_count < BIT_COUNT {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(fields) = Self::validate_forward_packet(&packet) {
|
||||
return Some((fields, true));
|
||||
}
|
||||
|
||||
if inverted {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(fields) = Self::validate_reverse_packet(&packet) {
|
||||
return Some((fields, false));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Build a DecodedSignal from validated fields. `crc_valid` reflects whether the forward
|
||||
/// (checksum-validated) path matched.
|
||||
fn build_signal(fields: &HondaStaticFields, crc_valid: bool) -> DecodedSignal {
|
||||
DecodedSignal {
|
||||
serial: Some(fields.serial),
|
||||
button: Some(fields.button),
|
||||
// KAT counters are u16; Honda's is 24-bit. Truncate to the low 16 bits for the field
|
||||
// (the full 24-bit counter is preserved in the packed `data` word).
|
||||
counter: Some(fields.counter as u16),
|
||||
crc_valid,
|
||||
data: Self::pack_compact(fields),
|
||||
data_count_bit: BIT_COUNT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a KAT generic button command to a Honda Static 4-bit button code.
|
||||
/// Honda button codes: Lock=1, Unlock=2, Trunk=4, Remote Start=5, Panic=8, Lock x2=9
|
||||
/// (see honda_static_button_names + honda_static_is_valid_button).
|
||||
fn map_button(button: u8) -> u8 {
|
||||
match button {
|
||||
BTN_LOCK => 1,
|
||||
BTN_UNLOCK => 2,
|
||||
BTN_TRUNK => 4,
|
||||
BTN_PANIC => 8,
|
||||
// Already a valid Honda code → pass through.
|
||||
b if Self::is_valid_button(b) => b,
|
||||
// honda_static_encoder_remap_button for codes 2..=5.
|
||||
b if (2..=5).contains(&b) => ENCODER_BUTTON_MAP[(b - 2) as usize],
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn enc_add_level(signal: &mut Vec<LevelDuration>, level: bool, duration: u32) {
|
||||
if let Some(last) = signal.last_mut() {
|
||||
if last.level == level {
|
||||
*last = LevelDuration::new(level, last.duration_us + duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
signal.push(LevelDuration::new(level, duration));
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for HondaStaticDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Honda Static"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: BIT_COUNT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[315_000_000, 433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.symbols.clear();
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
let sym = level;
|
||||
|
||||
// Short pulse → one symbol (matches the SHORT range check in the C feed).
|
||||
if duration >= SHORT_BASE_US && (duration - SHORT_BASE_US) <= SHORT_SPAN_US {
|
||||
if self.symbols.len() < SYMBOL_CAPACITY {
|
||||
self.symbols.push(sym);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
// Long pulse → two symbols (same level).
|
||||
if duration >= LONG_BASE_US && (duration - LONG_BASE_US) <= LONG_SPAN_US {
|
||||
if self.symbols.len() + 2 <= SYMBOL_CAPACITY {
|
||||
self.symbols.push(sym);
|
||||
self.symbols.push(sym);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
// Out-of-range pulse (sync 700µs or a gap): try to parse the buffered symbols, then reset.
|
||||
// Matches the C feed: parse with inverted=true first, then inverted=false.
|
||||
let mut result = None;
|
||||
if self.symbols.len() >= MIN_SYMBOLS {
|
||||
let parsed = Self::parse_symbols(&self.symbols, true)
|
||||
.or_else(|| Self::parse_symbols(&self.symbols, false));
|
||||
if let Some((fields, crc_valid)) = parsed {
|
||||
// Faithful to the C: both the forward (checksum-validated) and reverse
|
||||
// (button+serial-validated) packets commit a decode. The strong button/serial
|
||||
// gates keep this from false-matching (verified: zero false matches across the
|
||||
// IMPORTS sweep). `crc_valid` reflects whether the checksum-validated forward path
|
||||
// matched (true) vs. the reverse path (false).
|
||||
result = Some(Self::build_signal(&fields, crc_valid));
|
||||
}
|
||||
}
|
||||
|
||||
self.symbols.clear();
|
||||
result
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let serial = decoded.serial?;
|
||||
if !Self::is_valid_serial(serial) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let fields = HondaStaticFields {
|
||||
button: Self::map_button(button),
|
||||
serial,
|
||||
counter: decoded.counter.unwrap_or(0) as u32 & 0x00FF_FFFF,
|
||||
checksum: 0,
|
||||
};
|
||||
let packet = Self::build_packet_bytes(&fields);
|
||||
|
||||
// Matches honda_static_build_upload.
|
||||
let mut signal =
|
||||
Vec::with_capacity(1 + PREAMBLE_ALTERNATING_COUNT + 2 * BIT_COUNT + 1);
|
||||
|
||||
// Sync: HIGH 700µs.
|
||||
Self::enc_add_level(&mut signal, true, SYNC_TIME_US);
|
||||
|
||||
// Alternating preamble: 160 elements at 63µs, level = (i & 1) (starts LOW).
|
||||
for i in 0..PREAMBLE_ALTERNATING_COUNT {
|
||||
Self::enc_add_level(&mut signal, (i & 1) != 0, ELEMENT_TIME_US);
|
||||
}
|
||||
|
||||
// Data, MSB-first: bit → (!value 63µs, value 63µs).
|
||||
for bit in 0..BIT_COUNT {
|
||||
let shift = (!bit) & 0x07;
|
||||
let value = ((packet[bit >> 3] >> shift) & 1) != 0;
|
||||
Self::enc_add_level(&mut signal, !value, ELEMENT_TIME_US);
|
||||
Self::enc_add_level(&mut signal, value, ELEMENT_TIME_US);
|
||||
}
|
||||
|
||||
// Trailing sync: !last_bit for 700µs.
|
||||
let last_bit = (packet[7] & 1) != 0;
|
||||
Self::enc_add_level(&mut signal, !last_bit, SYNC_TIME_US);
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HondaStaticDecoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Round-trip: encode a known frame, feed it back through the decoder, and confirm the fields
|
||||
/// survive. The encoder emits the inverted-Manchester interpretation, which the decoder tries
|
||||
/// first.
|
||||
#[test]
|
||||
fn honda_static_encode_decode_roundtrip() {
|
||||
let serial = 0x0123456u32 & 0x0FFF_FFFF;
|
||||
let counter = 0x00AB12u32;
|
||||
let original = DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(BTN_UNLOCK),
|
||||
counter: Some(counter as u16),
|
||||
crc_valid: true,
|
||||
data: 0,
|
||||
data_count_bit: BIT_COUNT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
};
|
||||
|
||||
let decoder = HondaStaticDecoder::new();
|
||||
let signal = decoder.encode(&original, BTN_UNLOCK).expect("encode");
|
||||
|
||||
// Feed the encoded pulses, plus a terminating gap to flush the symbol buffer.
|
||||
let mut dec = HondaStaticDecoder::new();
|
||||
let mut decoded = None;
|
||||
for ld in &signal {
|
||||
if let Some(d) = dec.feed(ld.level, ld.duration_us) {
|
||||
decoded = Some(d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if decoded.is_none() {
|
||||
// Terminating out-of-range pulse to flush.
|
||||
decoded = dec.feed(false, 5000);
|
||||
}
|
||||
|
||||
let decoded = decoded.expect("expected a decode from the round-tripped frame");
|
||||
assert!(decoded.crc_valid, "checksum should validate");
|
||||
assert_eq!(decoded.serial, Some(serial), "serial mismatch");
|
||||
assert_eq!(decoded.button, Some(2), "button should map to Honda Unlock=2");
|
||||
assert_eq!(
|
||||
decoded.counter,
|
||||
Some(counter as u16),
|
||||
"counter (low 16 bits) mismatch"
|
||||
);
|
||||
}
|
||||
|
||||
/// The packed Key word must reproduce the C compact layout for a known field set.
|
||||
#[test]
|
||||
fn honda_static_pack_compact_layout() {
|
||||
let fields = HondaStaticFields {
|
||||
button: 0x02,
|
||||
serial: 0x0ABCDEF,
|
||||
counter: 0x123456,
|
||||
checksum: 0,
|
||||
};
|
||||
let data = HondaStaticDecoder::pack_compact(&fields);
|
||||
let bytes = data.to_be_bytes();
|
||||
// compact[0] = button & 0x0F
|
||||
assert_eq!(bytes[0], 0x02);
|
||||
// serial nibbles: >>20, >>12, >>4, <<4
|
||||
assert_eq!(bytes[1], (0x0ABCDEFu32 >> 20) as u8);
|
||||
assert_eq!(bytes[2], (0x0ABCDEFu32 >> 12) as u8);
|
||||
assert_eq!(bytes[3], (0x0ABCDEFu32 >> 4) as u8);
|
||||
assert_eq!(bytes[4], (0x0ABCDEFu32 << 4) as u8);
|
||||
// counter bytes
|
||||
assert_eq!(bytes[5], 0x12);
|
||||
assert_eq!(bytes[6], 0x34);
|
||||
assert_eq!(bytes[7], 0x56);
|
||||
}
|
||||
|
||||
/// build_packet_bytes + validate_forward_packet must round-trip the fields, and the checksum
|
||||
/// must validate.
|
||||
#[test]
|
||||
fn honda_static_packet_validate_roundtrip() {
|
||||
let fields = HondaStaticFields {
|
||||
button: 0x05,
|
||||
serial: 0x0FEDCBA,
|
||||
counter: 0x00FF01,
|
||||
checksum: 0,
|
||||
};
|
||||
let packet = HondaStaticDecoder::build_packet_bytes(&fields);
|
||||
let validated = HondaStaticDecoder::validate_forward_packet(&packet)
|
||||
.expect("forward packet should validate");
|
||||
assert_eq!(validated.button, 0x05);
|
||||
assert_eq!(validated.serial, 0x0FEDCBA);
|
||||
assert_eq!(validated.counter, 0x00FF01);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
//! Honda V1 protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/honda_v1.c` and
|
||||
//! `honda_v1.h`. Honda/Acura fixed-code keyfobs, a DIFFERENT protocol from `honda_static`.
|
||||
//! AM (OOK), 315 MHz + 433.92 MHz. The on-wire frame carries 68 bits: 64-bit data + a 4-bit
|
||||
//! CRC-fold nibble.
|
||||
//!
|
||||
//! **Encoding**: short/long pulse PWM. te_short=1000µs, te_long=2000µs, te_delta=400µs,
|
||||
//! te_end=3500µs, te_short_min=600µs. Each on-wire symbol is one pulse whose width selects 0/1,
|
||||
//! but the demodulated stream is glued together by a "pending bit" timing accumulator (see `feed`)
|
||||
//! before being classified.
|
||||
//!
|
||||
//! **Pending-bit accumulation** (matches `subghz_protocol_decoder_honda_v1_feed`): sub-`te_delta`
|
||||
//! runts are summed into `pending`; a HIGH level keeps extending the running HIGH pulse; a LOW
|
||||
//! level flushes the accumulated HIGH pulse (if it reached `te_short_min`) as a synthetic symbol,
|
||||
//! then the LOW pulse itself is classified. The symbol layer (`honda_v1_symbol`) walks a
|
||||
//! Reset→Preamble→Data state machine; in the Data step a short pulse toggles a `data_pending` flag
|
||||
//! and, when paired, emits the level as a bit, while a long pulse emits directly — this is the
|
||||
//! exact pending-bit logic ported from the C.
|
||||
//!
|
||||
//! **Frame / fields**: after the end gap (>te_end), `commit` requires ≥68 collected bits, then
|
||||
//! left-shifts the 12-byte bit buffer by `max(1, bit_count-68)` to drop leading preamble leakage
|
||||
//! and align the trailing frame. `data` = first 8 bytes (64 bits), `k2` (CRC nibble) = byte 8's
|
||||
//! high nibble. Fields (matches `honda_v1_decode_fields`): serial = data[63:36] (28b),
|
||||
//! button = data[31:28] (nibble), counter = data[15:0] (16b).
|
||||
//!
|
||||
//! **Validation / gating**: a button-code table (Unlock=0, Lock=8, Trunk=9, Panic=10) plus a
|
||||
//! CRC-fold checksum (`honda_v1_checksum*`). Emission is gated on the button being valid (matches
|
||||
//! the C `commit`); `crc_valid` reflects whether the received CRC nibble matches either wire-order
|
||||
//! checksum (`honda_v1_crc_valid`). The strong button gate keeps Honda V1 from false-matching.
|
||||
//!
|
||||
//! **Encoder** (matches `honda_v1_build_upload` / `honda_v1_append_frame`, behind
|
||||
//! `ENABLE_EMULATE_FEATURE`): builds the 64-bit key from serial/button/counter via the button
|
||||
//! table, then emits a 180-element short-pair preamble + 4 PWM frames (2 per checksum wire value).
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const BIT_COUNT: usize = 68;
|
||||
const TE_SHORT: u32 = 1000;
|
||||
const TE_LONG: u32 = 2000;
|
||||
const TE_DELTA: u32 = 400;
|
||||
const TE_SHORT_MIN: u32 = 600;
|
||||
const TE_END: u32 = 3500;
|
||||
const VALID_MAX: u8 = 0x4B; // honda_v1_add_bit cap (75)
|
||||
const NIBBLE_MASK: u8 = 0x0F;
|
||||
const SERIAL_MASK: u32 = 0x0FFF_FFFF;
|
||||
const COUNTER_MASK: u16 = 0xFFFF;
|
||||
const BUTTON_MAX: u8 = 10;
|
||||
const BUTTON_VALID_MASK: u16 = 0x701; // bits set for Unlock(0), Lock(8), Trunk(9), Panic(10)
|
||||
const DECODE_BUFFER_BYTES: usize = 12;
|
||||
|
||||
// Encoder constants (honda_v1.c, ENABLE_EMULATE_FEATURE).
|
||||
const PREAMBLE_UPLOAD_COUNT: usize = 180;
|
||||
const FRAME_SYMBOLS: usize = 80;
|
||||
const FRAME_START: usize = 12;
|
||||
const FRAME_SYNC_DROP: usize = 2;
|
||||
const FRAME_REPEAT_PER_CRC: usize = 2;
|
||||
const FRAME_GAP_US: u32 = 5000;
|
||||
const FRAME_CRC_INDEX: usize = 8;
|
||||
|
||||
// HondaV1Button codes (the on-wire button nibble at data[31:28]).
|
||||
const BTN_CODE_UNLOCK: u8 = 0;
|
||||
const BTN_CODE_LOCK: u8 = 8;
|
||||
const BTN_CODE_TRUNK: u8 = 9;
|
||||
const BTN_CODE_PANIC: u8 = 10;
|
||||
|
||||
// honda_v1_button_codes[] (24-bit table values, used to rebuild the key in the encoder).
|
||||
const BUTTON_CODE_UNLOCK: u32 = 0x0008_0808;
|
||||
const BUTTON_CODE_LOCK: u32 = 0x0008_8888;
|
||||
const BUTTON_CODE_TRUNK: u32 = 0x0009_9190;
|
||||
const BUTTON_CODE_PANIC: u32 = 0x000F_A7A0;
|
||||
const BUTTON_FALLBACK_CODE: u32 = 0x0008_8888;
|
||||
|
||||
// KAT generic button codes.
|
||||
const BTN_LOCK: u8 = 0x01;
|
||||
const BTN_UNLOCK: u8 = 0x02;
|
||||
const BTN_TRUNK: u8 = 0x04;
|
||||
const BTN_PANIC: u8 = 0x08;
|
||||
|
||||
/// Decoder steps (matches HondaV1DecoderStep).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Preamble,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// Honda V1 decoder (matches SubGhzProtocolDecoderHondaV1).
|
||||
pub struct HondaV1Decoder {
|
||||
step: DecoderStep,
|
||||
preamble_count: u8,
|
||||
preamble_has_long: bool,
|
||||
data_pending: bool,
|
||||
last_level: bool,
|
||||
bits: [u8; DECODE_BUFFER_BYTES],
|
||||
bit_count: u8,
|
||||
// Pending-bit timing accumulator (decoder-level, persists across symbol resets).
|
||||
pending: u32,
|
||||
pending_valid: bool,
|
||||
}
|
||||
|
||||
impl HondaV1Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
preamble_count: 0,
|
||||
preamble_has_long: false,
|
||||
data_pending: false,
|
||||
last_level: false,
|
||||
bits: [0u8; DECODE_BUFFER_BYTES],
|
||||
bit_count: 0,
|
||||
pending: 0,
|
||||
pending_valid: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// honda_v1_button_valid: button <= 10 && ((0x701 >> button) & 1).
|
||||
fn button_valid(b: u8) -> bool {
|
||||
if b > BUTTON_MAX {
|
||||
return false;
|
||||
}
|
||||
((BUTTON_VALID_MASK >> b) & 1) != 0
|
||||
}
|
||||
|
||||
/// honda_v1_duration_is: |d - t| <= te_delta (saturating both directions).
|
||||
fn duration_is(d: u32, t: u32) -> bool {
|
||||
if d >= t {
|
||||
(d - t) <= TE_DELTA
|
||||
} else {
|
||||
(t - d) <= TE_DELTA
|
||||
}
|
||||
}
|
||||
|
||||
/// honda_v1_crc_fold.
|
||||
fn crc_fold(v: u16) -> u8 {
|
||||
let lo = (v & (NIBBLE_MASK as u16)) as u8;
|
||||
let hi = v >> 4;
|
||||
let s: i32 = if (hi & 1) != 0 {
|
||||
lo as i32
|
||||
} else {
|
||||
-(lo as i32)
|
||||
};
|
||||
let mut out = ((s - (hi as i32)) & 7) as u8;
|
||||
out |= (((v >> 3) & 1) as u8) << 3;
|
||||
if ((v >> 1) & 1) != 0 && (((v >> 4) ^ (v >> 5)) & 1) != 0 {
|
||||
out ^= 0x04;
|
||||
}
|
||||
out & NIBBLE_MASK
|
||||
}
|
||||
|
||||
/// honda_v1_checksum_base.
|
||||
fn checksum_base(data: u64) -> u8 {
|
||||
let a = Self::crc_fold((data & (COUNTER_MASK as u64)) as u16);
|
||||
let b = Self::crc_fold(((data >> 40) & 0xFF) as u16);
|
||||
(a ^ b ^ 1) & NIBBLE_MASK
|
||||
}
|
||||
|
||||
/// honda_v1_checksum_alternate.
|
||||
fn checksum_alternate(checksum: u8) -> u8 {
|
||||
let mut mask = 0x09u8;
|
||||
if (checksum & 1) == 0 {
|
||||
mask = if (checksum & 2) != 0 { 0x0B } else { NIBBLE_MASK };
|
||||
}
|
||||
(checksum ^ mask) & NIBBLE_MASK
|
||||
}
|
||||
|
||||
/// honda_v1_checksum_wire_order → (first, second).
|
||||
fn checksum_wire_order(data: u64) -> (u8, u8) {
|
||||
let checksum = Self::checksum_base(data);
|
||||
let other = Self::checksum_alternate(checksum);
|
||||
if (checksum & 0x08) != 0 {
|
||||
(other, checksum)
|
||||
} else {
|
||||
(checksum, other)
|
||||
}
|
||||
}
|
||||
|
||||
/// honda_v1_crc_valid: received nibble matches either wire-order checksum.
|
||||
fn crc_valid(data: u64, crc: u8) -> bool {
|
||||
let (first, second) = Self::checksum_wire_order(data);
|
||||
let crc = crc & NIBBLE_MASK;
|
||||
crc == first || crc == second
|
||||
}
|
||||
|
||||
/// honda_v1_decode_fields. Returns (serial, button, counter).
|
||||
fn decode_fields(data: u64) -> (u32, u8, u16) {
|
||||
let low = (data & 0xFFFF_FFFF) as u32;
|
||||
let serial = ((data >> 36) & (SERIAL_MASK as u64)) as u32;
|
||||
let button = ((low >> 28) & (NIBBLE_MASK as u32)) as u8;
|
||||
let counter = (low & (COUNTER_MASK as u32)) as u16;
|
||||
(serial, button, counter)
|
||||
}
|
||||
|
||||
/// honda_v1_button_code (encoder).
|
||||
fn button_code(button: u8) -> u32 {
|
||||
if !Self::button_valid(button) {
|
||||
return BUTTON_FALLBACK_CODE;
|
||||
}
|
||||
match button {
|
||||
BTN_CODE_UNLOCK => BUTTON_CODE_UNLOCK,
|
||||
BTN_CODE_LOCK => BUTTON_CODE_LOCK,
|
||||
BTN_CODE_TRUNK => BUTTON_CODE_TRUNK,
|
||||
BTN_CODE_PANIC => BUTTON_CODE_PANIC,
|
||||
_ => BUTTON_FALLBACK_CODE,
|
||||
}
|
||||
}
|
||||
|
||||
/// honda_v1_build_key.
|
||||
fn build_key(serial: u32, button: u8, counter: u16) -> u64 {
|
||||
let table = Self::button_code(button);
|
||||
let low = ((table & (COUNTER_MASK as u32)) << 16) | (counter as u32);
|
||||
let high = ((serial & SERIAL_MASK) << 4) | (table >> 16);
|
||||
((high as u64) << 32) | (low as u64)
|
||||
}
|
||||
|
||||
/// Map a KAT generic button command to a Honda V1 button code (nibble at data[31:28]).
|
||||
fn map_button(button: u8) -> u8 {
|
||||
match button {
|
||||
BTN_LOCK => BTN_CODE_LOCK,
|
||||
BTN_UNLOCK => BTN_CODE_UNLOCK,
|
||||
BTN_TRUNK => BTN_CODE_TRUNK,
|
||||
BTN_PANIC => BTN_CODE_PANIC,
|
||||
// Already a valid Honda V1 code → pass through.
|
||||
b if Self::button_valid(b) => b,
|
||||
_ => BTN_CODE_UNLOCK,
|
||||
}
|
||||
}
|
||||
|
||||
/// honda_v1_state_reset (symbol-layer state only; does NOT touch pending accumulator).
|
||||
fn state_reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.preamble_count = 0;
|
||||
self.preamble_has_long = false;
|
||||
self.data_pending = false;
|
||||
self.last_level = false;
|
||||
self.bit_count = 0;
|
||||
self.bits = [0u8; DECODE_BUFFER_BYTES];
|
||||
}
|
||||
|
||||
/// honda_v1_add_bit: MSB-first into bits[], capped at VALID_MAX.
|
||||
fn add_bit(&mut self, bit: bool) {
|
||||
if self.bit_count > VALID_MAX {
|
||||
return;
|
||||
}
|
||||
if bit {
|
||||
let byte = (self.bit_count >> 3) as usize;
|
||||
let shift = (!self.bit_count) & 0x07;
|
||||
self.bits[byte] |= 1u8 << shift;
|
||||
}
|
||||
self.bit_count += 1;
|
||||
}
|
||||
|
||||
/// honda_v1_commit: align trailing 68-bit frame, validate button, return signal on success.
|
||||
fn commit(&mut self) -> Option<DecodedSignal> {
|
||||
if (self.bit_count as usize) < BIT_COUNT {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut aligned = self.bits;
|
||||
|
||||
let mut shift_count = self.bit_count - BIT_COUNT as u8;
|
||||
if shift_count < 1 {
|
||||
shift_count = 1;
|
||||
}
|
||||
|
||||
for _ in 0..shift_count {
|
||||
for i in 0..(DECODE_BUFFER_BYTES - 1) {
|
||||
aligned[i] = (aligned[i] << 1) | (aligned[i + 1] >> 7);
|
||||
}
|
||||
aligned[DECODE_BUFFER_BYTES - 1] <<= 1;
|
||||
}
|
||||
|
||||
let button = aligned[4] >> 4;
|
||||
if !Self::button_valid(button) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let data = u64::from_be_bytes(aligned[0..8].try_into().unwrap());
|
||||
let k2 = aligned[8] >> 4;
|
||||
let (serial, btn, counter) = Self::decode_fields(data);
|
||||
let crc_valid = Self::crc_valid(data, k2);
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(btn),
|
||||
counter: Some(counter),
|
||||
crc_valid,
|
||||
data,
|
||||
data_count_bit: BIT_COUNT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// honda_v1_symbol: classify a (level, duration) pulse and drive the state machine.
|
||||
/// Returns Some(signal) when the end-gap path commits a frame.
|
||||
fn symbol(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
let sh = Self::duration_is(duration, TE_SHORT);
|
||||
let lg = Self::duration_is(duration, TE_LONG);
|
||||
|
||||
if !sh && !lg {
|
||||
let mut result = None;
|
||||
if !level && duration > TE_END && self.step == DecoderStep::Data {
|
||||
result = self.commit();
|
||||
}
|
||||
self.state_reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
if self.step == DecoderStep::Reset {
|
||||
if level {
|
||||
self.step = DecoderStep::Preamble;
|
||||
self.preamble_count = 1;
|
||||
self.last_level = level;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.step == DecoderStep::Preamble {
|
||||
if lg {
|
||||
// honda_v1.c: if(preamble_count < 0xFF) preamble_count++ (saturating).
|
||||
self.preamble_count = self.preamble_count.saturating_add(1);
|
||||
self.preamble_has_long = true;
|
||||
self.last_level = level;
|
||||
return None;
|
||||
}
|
||||
|
||||
if sh {
|
||||
if self.preamble_has_long && self.preamble_count > 5 {
|
||||
self.step = DecoderStep::Data;
|
||||
self.bit_count = 0;
|
||||
self.bits = [0u8; DECODE_BUFFER_BYTES];
|
||||
self.data_pending = true;
|
||||
self.last_level = level;
|
||||
return None;
|
||||
}
|
||||
|
||||
self.preamble_count = self.preamble_count.saturating_add(1);
|
||||
self.last_level = level;
|
||||
return None;
|
||||
}
|
||||
|
||||
self.state_reset();
|
||||
return None;
|
||||
}
|
||||
|
||||
// Data step: pending-bit accumulation.
|
||||
if sh {
|
||||
if self.data_pending {
|
||||
self.add_bit(level);
|
||||
self.data_pending = false;
|
||||
self.last_level = level;
|
||||
} else {
|
||||
self.data_pending = true;
|
||||
self.last_level = level;
|
||||
}
|
||||
} else {
|
||||
// long pulse
|
||||
if self.data_pending {
|
||||
self.add_bit(level);
|
||||
} else {
|
||||
self.add_bit(self.last_level);
|
||||
}
|
||||
self.last_level = level;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn enc_add_level(signal: &mut Vec<LevelDuration>, level: bool, duration: u32) {
|
||||
if let Some(last) = signal.last_mut() {
|
||||
if last.level == level {
|
||||
*last = LevelDuration::new(level, last.duration_us + duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
signal.push(LevelDuration::new(level, duration));
|
||||
}
|
||||
|
||||
/// honda_v1_append_frame: emit one PWM frame (12-symbol sync header + 68 data symbols),
|
||||
/// dropping the first FRAME_SYNC_DROP entries, then a tail + inter-frame gap.
|
||||
fn append_frame(signal: &mut Vec<LevelDuration>, frame: &[u8; 9]) {
|
||||
// Build the per-frame symbol stream with merge semantics (pp_emit_merge).
|
||||
let mut generated: Vec<LevelDuration> = Vec::with_capacity(FRAME_SYMBOLS * 2);
|
||||
for bit_index in 0..FRAME_SYMBOLS {
|
||||
let bit = if bit_index >= FRAME_START {
|
||||
let data_index = (bit_index - FRAME_START) >> 3;
|
||||
let shift = (11i32 - bit_index as i32) & 0x07;
|
||||
((frame[data_index] >> shift) & 0x01) != 0
|
||||
} else {
|
||||
((!bit_index) & 0x01) != 0
|
||||
};
|
||||
// bit -> (level=bit, te)(level=!bit, te), merged.
|
||||
Self::enc_add_level(&mut generated, bit, TE_SHORT);
|
||||
Self::enc_add_level(&mut generated, !bit, TE_SHORT);
|
||||
}
|
||||
|
||||
if generated.len() <= FRAME_SYNC_DROP {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy generated[FRAME_SYNC_DROP..] into the upload (still merging at the seam).
|
||||
for ld in &generated[FRAME_SYNC_DROP..] {
|
||||
Self::enc_add_level(signal, ld.level, ld.duration_us);
|
||||
}
|
||||
|
||||
// Tail: !last_level for te_short; if that was low, add a high te_short; then the gap.
|
||||
let last_level = signal.last().map(|l| l.level).unwrap_or(false);
|
||||
let tail_level = !last_level;
|
||||
Self::enc_add_level(signal, tail_level, TE_SHORT);
|
||||
if !tail_level {
|
||||
Self::enc_add_level(signal, true, TE_SHORT);
|
||||
}
|
||||
Self::enc_add_level(signal, false, FRAME_GAP_US);
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for HondaV1Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Honda V1"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: BIT_COUNT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[315_000_000, 433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.pending = 0;
|
||||
self.pending_valid = false;
|
||||
self.state_reset();
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
// Pending-bit timing accumulation (subghz_protocol_decoder_honda_v1_feed).
|
||||
if duration < TE_DELTA {
|
||||
self.pending = self.pending.saturating_add(duration);
|
||||
self.pending_valid = true;
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut result = None;
|
||||
|
||||
if self.pending_valid {
|
||||
let p = self.pending;
|
||||
if level {
|
||||
self.pending = p.saturating_add(duration);
|
||||
self.pending_valid = true;
|
||||
return None;
|
||||
}
|
||||
if p >= TE_SHORT_MIN {
|
||||
result = self.symbol(true, p);
|
||||
}
|
||||
self.pending = 0;
|
||||
self.pending_valid = false;
|
||||
}
|
||||
|
||||
if level {
|
||||
self.pending = duration;
|
||||
self.pending_valid = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// A symbol committed on the flushed HIGH pulse takes priority (the C calls back there);
|
||||
// otherwise classify this LOW pulse.
|
||||
let low_result = self.symbol(false, duration);
|
||||
result.or(low_result)
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let serial = decoded.serial?;
|
||||
let counter = decoded.counter.unwrap_or(0) & COUNTER_MASK;
|
||||
let btn = Self::map_button(button);
|
||||
let data = Self::build_key(serial & SERIAL_MASK, btn, counter);
|
||||
|
||||
let mut frame = [0u8; 9];
|
||||
frame[0..8].copy_from_slice(&data.to_be_bytes());
|
||||
let (first, second) = Self::checksum_wire_order(data);
|
||||
|
||||
let mut signal: Vec<LevelDuration> = Vec::with_capacity(PREAMBLE_UPLOAD_COUNT + 8 * FRAME_SYMBOLS);
|
||||
|
||||
// Preamble: 180 short entries (90 H/L pairs); the final LOW becomes a 5000µs gap.
|
||||
for _ in 0..(PREAMBLE_UPLOAD_COUNT / 2) {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
if let Some(last) = signal.last_mut() {
|
||||
*last = LevelDuration::new(false, FRAME_GAP_US);
|
||||
}
|
||||
|
||||
// 4 frames: 2 per checksum wire value (first, then second).
|
||||
for &crc in &[first, second] {
|
||||
frame[FRAME_CRC_INDEX] = crc << 4;
|
||||
for _ in 0..FRAME_REPEAT_PER_CRC {
|
||||
Self::append_frame(&mut signal, &frame);
|
||||
}
|
||||
}
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HondaV1Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// build_key + decode_fields must round-trip the fields, and the encoder's CRC nibble must
|
||||
/// validate against honda_v1_crc_valid.
|
||||
#[test]
|
||||
fn honda_v1_key_field_roundtrip() {
|
||||
let serial = 0x0ABCDEFu32 & SERIAL_MASK;
|
||||
let counter = 0x1234u16;
|
||||
let data = HondaV1Decoder::build_key(serial, BTN_CODE_TRUNK, counter);
|
||||
let (s, b, c) = HondaV1Decoder::decode_fields(data);
|
||||
assert_eq!(s, serial, "serial mismatch");
|
||||
assert_eq!(b, BTN_CODE_TRUNK, "button mismatch");
|
||||
assert_eq!(c, counter, "counter mismatch");
|
||||
|
||||
let (first, _second) = HondaV1Decoder::checksum_wire_order(data);
|
||||
assert!(
|
||||
HondaV1Decoder::crc_valid(data, first),
|
||||
"wire-order checksum should validate"
|
||||
);
|
||||
}
|
||||
|
||||
/// Full encode → decode round-trip: emit a frame for known fields, feed the pulses back through
|
||||
/// the decoder (the pending-bit accumulator + symbol state machine), and confirm the fields and
|
||||
/// checksum survive.
|
||||
#[test]
|
||||
fn honda_v1_encode_decode_roundtrip() {
|
||||
let serial = 0x0123456u32 & SERIAL_MASK;
|
||||
let counter = 0x00ABu16;
|
||||
let original = DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(BTN_UNLOCK),
|
||||
counter: Some(counter),
|
||||
crc_valid: true,
|
||||
data: 0,
|
||||
data_count_bit: BIT_COUNT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
};
|
||||
|
||||
let decoder = HondaV1Decoder::new();
|
||||
let signal = decoder.encode(&original, BTN_UNLOCK).expect("encode");
|
||||
|
||||
let mut dec = HondaV1Decoder::new();
|
||||
let mut decoded = None;
|
||||
for ld in &signal {
|
||||
if let Some(d) = dec.feed(ld.level, ld.duration_us) {
|
||||
decoded = Some(d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Flush with a terminating end-gap if the inter-frame gaps didn't already commit.
|
||||
if decoded.is_none() {
|
||||
decoded = dec.feed(false, TE_END + 1000);
|
||||
}
|
||||
|
||||
let decoded = decoded.expect("expected a decode from the round-tripped frame");
|
||||
assert!(decoded.crc_valid, "checksum should validate");
|
||||
assert_eq!(decoded.serial, Some(serial), "serial mismatch");
|
||||
assert_eq!(
|
||||
decoded.button,
|
||||
Some(BTN_CODE_UNLOCK),
|
||||
"button should map to Honda V1 Unlock=0"
|
||||
);
|
||||
assert_eq!(decoded.counter, Some(counter), "counter mismatch");
|
||||
assert_eq!(decoded.data_count_bit, BIT_COUNT);
|
||||
}
|
||||
|
||||
/// Button validity mask must match honda_v1_button_valid (0x701 → Unlock/Lock/Trunk/Panic).
|
||||
#[test]
|
||||
fn honda_v1_button_validity() {
|
||||
assert!(HondaV1Decoder::button_valid(BTN_CODE_UNLOCK));
|
||||
assert!(HondaV1Decoder::button_valid(BTN_CODE_LOCK));
|
||||
assert!(HondaV1Decoder::button_valid(BTN_CODE_TRUNK));
|
||||
assert!(HondaV1Decoder::button_valid(BTN_CODE_PANIC));
|
||||
// Invalid codes.
|
||||
assert!(!HondaV1Decoder::button_valid(1));
|
||||
assert!(!HondaV1Decoder::button_valid(7));
|
||||
assert!(!HondaV1Decoder::button_valid(11));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
//! KeeLoq barrier/gate/garage and alarm manufacturer names.
|
||||
//! When a decode matches one of these, the signal action menu shows only Replay (and export/delete),
|
||||
//! not TX Lock/Unlock/Trunk/Panic (those are for car keyfobs).
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
fn normalize(s: &str) -> String {
|
||||
s.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric())
|
||||
.flat_map(|c| c.to_lowercase())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Barrier/gate/garage door manufacturers (normalized names).
|
||||
/// Covers Flipper/ProtoPirate-style names and common variants (e.g. NICE_MHOUSE, Guard_RF-311A).
|
||||
const BARRIER_NAMES: &[&str] = &[
|
||||
"airforce",
|
||||
"allmatic",
|
||||
"alutechat4n",
|
||||
"ansonic",
|
||||
"aprimatic",
|
||||
"beninca",
|
||||
"benincavaaes128",
|
||||
"bett",
|
||||
"bft",
|
||||
"cameatomo",
|
||||
"cameatomotop44rbn",
|
||||
"camespace",
|
||||
"camestatic",
|
||||
"cameweetwin",
|
||||
"chamberlaincode",
|
||||
"clemsa",
|
||||
"comunello",
|
||||
"deamio",
|
||||
"dickertmahs",
|
||||
"doitrand",
|
||||
"doorhan",
|
||||
"dooya",
|
||||
"dtmneo",
|
||||
"ecostar",
|
||||
"elmespoland",
|
||||
"elplast",
|
||||
"faacrctx",
|
||||
"faacslh",
|
||||
"faacslhspa",
|
||||
"feron",
|
||||
"gatetx",
|
||||
"gbidi",
|
||||
"geniusbravo",
|
||||
"geniusbravoecho",
|
||||
"gibidi",
|
||||
"gsn",
|
||||
"guardrf311a",
|
||||
"gangqi",
|
||||
"hay21",
|
||||
"hollarm",
|
||||
"holtek",
|
||||
"holtekht12x",
|
||||
"honeywell",
|
||||
"honeywellwdb",
|
||||
"hormannbisecur",
|
||||
"hormannhsm",
|
||||
"ido",
|
||||
"intertechnov3",
|
||||
"ironlogic",
|
||||
"jcmtech",
|
||||
"jollymotors",
|
||||
"kingatesstylo4k",
|
||||
"legrand",
|
||||
"linear",
|
||||
"lineardelta3",
|
||||
"magellan",
|
||||
"marantec",
|
||||
"marantec24",
|
||||
"mastercode",
|
||||
"megacode",
|
||||
"merlin",
|
||||
"monarch",
|
||||
"motorline",
|
||||
"mutancode",
|
||||
"mutancomutancode",
|
||||
"neroradio",
|
||||
"nerosketch",
|
||||
"niceflo",
|
||||
"niceflorsone",
|
||||
"nicemhouse",
|
||||
"nicesmilo",
|
||||
"novoferm",
|
||||
"pecninin",
|
||||
"pecnin",
|
||||
"phoenixv2",
|
||||
"powersmart",
|
||||
"prastel",
|
||||
"princeton",
|
||||
"reversrb2",
|
||||
"roger",
|
||||
"rosh",
|
||||
"rossi",
|
||||
"sea",
|
||||
"secplusv1v2",
|
||||
"smc5326",
|
||||
"somfykeytis",
|
||||
"somfytelis",
|
||||
"steelmate",
|
||||
"stilmatic",
|
||||
];
|
||||
|
||||
/// Alarm (car alarm / aftermarket) manufacturers (normalized names).
|
||||
/// Same menu behaviour as barriers: Replay + export + delete only.
|
||||
const ALARM_NAMES: &[&str] = &[
|
||||
"a2a4",
|
||||
"sla2a4",
|
||||
"a6a9",
|
||||
"sla6a9",
|
||||
"alligator",
|
||||
"alligators275",
|
||||
"aps1100",
|
||||
"aps2550",
|
||||
"aps1100aps2550",
|
||||
"b6b9",
|
||||
"slb6b9dop",
|
||||
"cenmaxst5",
|
||||
"cenmaxst7",
|
||||
"cenmax",
|
||||
"cfm",
|
||||
"faraon",
|
||||
"harpoon",
|
||||
"jaguar",
|
||||
"kgd",
|
||||
"leopard",
|
||||
"mongoose",
|
||||
"panteraclk",
|
||||
"panteraxsjaguar",
|
||||
"partisanrx",
|
||||
"pro1",
|
||||
"pro2",
|
||||
"pandorapro2",
|
||||
"reff",
|
||||
"sheriff",
|
||||
"tomahawk9010",
|
||||
"tomahawkzx35",
|
||||
"tomahawktz9030",
|
||||
"tz9030",
|
||||
"starline",
|
||||
"zx730",
|
||||
"zx750",
|
||||
"zx755",
|
||||
"zx930",
|
||||
"zx940",
|
||||
"zx1070",
|
||||
"zx1090",
|
||||
"zx7307501055",
|
||||
];
|
||||
|
||||
fn barrier_set() -> &'static HashSet<String> {
|
||||
static BARRIER_SET: OnceLock<HashSet<String>> = OnceLock::new();
|
||||
BARRIER_SET.get_or_init(|| BARRIER_NAMES.iter().map(|s| (*s).to_string()).collect())
|
||||
}
|
||||
|
||||
fn alarm_set() -> &'static HashSet<String> {
|
||||
static ALARM_SET: OnceLock<HashSet<String>> = OnceLock::new();
|
||||
ALARM_SET.get_or_init(|| ALARM_NAMES.iter().map(|s| (*s).to_string()).collect())
|
||||
}
|
||||
|
||||
/// Returns true if the protocol string is KeeLoq with a barrier/gate/garage door manufacturer.
|
||||
/// Protocol is typically "KeeLoq (ManufacturerName)" e.g. "KeeLoq (BFT)" or "KeeLoq (NICE_MHOUSE)".
|
||||
pub fn is_keeloq_barrier(protocol: &str) -> bool {
|
||||
let protocol = protocol.trim();
|
||||
let open = protocol.find(" (");
|
||||
let Some(open_idx) = open else { return false };
|
||||
if !protocol[..open_idx].eq_ignore_ascii_case("keeloq") {
|
||||
return false;
|
||||
}
|
||||
let rest = protocol[open_idx + 2..].trim_start();
|
||||
let close = rest.rfind(')');
|
||||
let Some(close_idx) = close else { return false };
|
||||
let inner = rest[..close_idx].trim();
|
||||
if inner.is_empty() {
|
||||
return false;
|
||||
}
|
||||
barrier_set().contains(&normalize(inner))
|
||||
}
|
||||
|
||||
/// Returns true if the protocol string is KeeLoq with an alarm (car alarm / aftermarket) manufacturer.
|
||||
/// Same menu behaviour as barriers: Replay + export + delete only, no TX Lock/Unlock/Trunk/Panic.
|
||||
pub fn is_keeloq_alarm(protocol: &str) -> bool {
|
||||
let protocol = protocol.trim();
|
||||
let open = protocol.find(" (");
|
||||
let Some(open_idx) = open else { return false };
|
||||
if !protocol[..open_idx].eq_ignore_ascii_case("keeloq") {
|
||||
return false;
|
||||
}
|
||||
let rest = protocol[open_idx + 2..].trim_start();
|
||||
let close = rest.rfind(')');
|
||||
let Some(close_idx) = close else { return false };
|
||||
let inner = rest[..close_idx].trim();
|
||||
if inner.is_empty() {
|
||||
return false;
|
||||
}
|
||||
alarm_set().contains(&normalize(inner))
|
||||
}
|
||||
|
||||
/// True if this KeeLoq protocol should hide car-style TX actions (barrier/gate or alarm).
|
||||
pub fn is_keeloq_non_car(protocol: &str) -> bool {
|
||||
is_keeloq_barrier(protocol) || is_keeloq_alarm(protocol)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn barrier_detection() {
|
||||
assert!(is_keeloq_barrier("KeeLoq (BFT)"));
|
||||
assert!(is_keeloq_barrier("KeeLoq (DoorHan)"));
|
||||
assert!(is_keeloq_barrier("KeeLoq (NICE_MHOUSE)"));
|
||||
assert!(is_keeloq_barrier("KeeLoq (Guard_RF-311A)"));
|
||||
assert!(is_keeloq_barrier("KeeLoq (Stilmatic)"));
|
||||
assert!(is_keeloq_barrier("KeeLoq (Motorline)"));
|
||||
assert!(!is_keeloq_barrier("KeeLoq (Star Line)"));
|
||||
assert!(!is_keeloq_barrier("Unknown"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alarm_detection() {
|
||||
assert!(is_keeloq_alarm("KeeLoq (Star Line)"));
|
||||
assert!(is_keeloq_alarm("KeeLoq (Pantera_CLK)"));
|
||||
assert!(is_keeloq_alarm("KeeLoq (Sheriff)"));
|
||||
assert!(is_keeloq_alarm("KeeLoq (Alligator_S-275)"));
|
||||
assert!(is_keeloq_alarm("KeeLoq (Harpoon)"));
|
||||
assert!(is_keeloq_alarm("KeeLoq (Partisan_RX)"));
|
||||
assert!(is_keeloq_alarm("KeeLoq (Cenmax_St-7)"));
|
||||
assert!(is_keeloq_alarm("KeeLoq (Reff)"));
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,9 @@ fn g5(x: u32, a: u32, b: u32, c: u32, d: u32, e: u32) -> u32 {
|
||||
pub fn keeloq_decrypt(data: u32, key: u64) -> u32 {
|
||||
let mut x = data;
|
||||
for r in 0..528u32 {
|
||||
let key_bit = ((key >> ((15 - r) & 63)) & 1) as u32;
|
||||
// (15 - r) relies on unsigned wraparound (matches the C reference `(15 - r) & 63`);
|
||||
// use wrapping_sub so debug builds don't panic on the overflow that release silently wraps.
|
||||
let key_bit = ((key >> ((15u32.wrapping_sub(r)) & 63)) & 1) as u32;
|
||||
let new_lsb = bit(x, 31) ^ bit(x, 15) ^ key_bit
|
||||
^ bit(KEELOQ_NLF, g5(x, 0, 8, 19, 25, 30));
|
||||
x = (x << 1) ^ new_lsb;
|
||||
|
||||
+8
-203
@@ -1,9 +1,11 @@
|
||||
//! Key management module for protocol encryption/decryption
|
||||
//!
|
||||
//! All keys are loaded from the **embedded keystore only** (no keystore.ini or file-based keys).
|
||||
//! The blob is built from the master list `REFERENCES/mf_keys.txt` by
|
||||
//! `scripts/build_keystore_from_mf_keys.py` and embedded in `crate::keystore::embedded`.
|
||||
//!
|
||||
//! Aligned with ProtoPirate's keys.c (KIA_KEY1..4, get_kia_mf_key, etc.).
|
||||
//! Keys are loaded from the embedded keystore blob in `crate::keystore` at startup
|
||||
//! via `load_keystore_from_embedded()`, which parses the blob from
|
||||
//! `keystore::embedded::KEYSTORE_BLOB` and populates:
|
||||
//! At startup `load_keystore_from_embedded()` parses `keystore::embedded::KEYSTORE_BLOB` and populates:
|
||||
//!
|
||||
//! - **KIA**: type 10 → kia_mf_key, 11 → kia_v6_a_key, 12 → kia_v6_b_key, 13 → kia_v5_key
|
||||
//! - **Star Line**: type 20 → star_line_mf_key
|
||||
@@ -13,10 +15,8 @@
|
||||
//! KIA V5 uses `get_kia_v5_key()`, KIA V6 uses `get_kia_v6_keystore_a()` / `get_kia_v6_keystore_b()`.
|
||||
|
||||
use super::aut64::{self, Aut64Key, AUT64_KEY_STRUCT_PACKED_SIZE};
|
||||
use configparser::ini::Ini;
|
||||
use std::path::Path;
|
||||
use std::sync::{OnceLock, RwLock};
|
||||
use tracing::{info, warn, error};
|
||||
use tracing::{error, info};
|
||||
|
||||
/// Key type identifiers; must match type IDs in `keystore::embedded::KEYSTORE_BLOB`.
|
||||
const KIA_KEY1: u32 = 10; // kia_mf_key (KIA V3/V4)
|
||||
@@ -81,7 +81,8 @@ impl KeyStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load VAG AUT64 keys from raw binary data (16 bytes per key; up to MAX_VAG_KEYS)
|
||||
/// Load VAG AUT64 keys from raw binary data (16 bytes per key; up to MAX_VAG_KEYS).
|
||||
/// Used only by the embedded keystore parser.
|
||||
pub fn load_vag_keys_from_data(&mut self, data: &[u8]) {
|
||||
if self.vag_keys_loaded {
|
||||
return;
|
||||
@@ -103,28 +104,6 @@ impl KeyStore {
|
||||
info!("Loaded {} VAG keys", self.vag_keys.len());
|
||||
}
|
||||
|
||||
/// Load VAG AUT64 keys from a file path
|
||||
pub fn load_vag_keys_from_file(&mut self, path: &str) {
|
||||
if self.vag_keys_loaded {
|
||||
return;
|
||||
}
|
||||
|
||||
let file_path = Path::new(path);
|
||||
if !file_path.exists() {
|
||||
warn!("VAG key file not found: {}", path);
|
||||
return;
|
||||
}
|
||||
|
||||
match std::fs::read(file_path) {
|
||||
Ok(data) => {
|
||||
self.load_vag_keys_from_data(&data);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read VAG key file {}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a VAG AUT64 key by its internal index field
|
||||
pub fn get_vag_key(&self, index: u8) -> Option<&Aut64Key> {
|
||||
self.vag_keys.iter().find(|k| k.index == index)
|
||||
@@ -183,12 +162,6 @@ pub fn load_keys(kia_entries: &[(u32, u64)]) {
|
||||
store.load_kia_keys(kia_entries);
|
||||
}
|
||||
|
||||
/// Initialize VAG keys from file
|
||||
pub fn load_vag_keys(path: &str) {
|
||||
let mut store = get_keystore_mut();
|
||||
store.load_vag_keys_from_file(path);
|
||||
}
|
||||
|
||||
/// Load the global keystore from the embedded blob (src/keystore/embedded.rs).
|
||||
/// Populates KIA (V3/V4, V5, V6A, V6B), Star Line, and VAG AUT64 keys from the blob.
|
||||
/// VAG raw bytes are 64 bytes = 4 × 16-byte packed keys; each key's `index` is byte 0 (used by VAG lookup).
|
||||
@@ -209,171 +182,3 @@ pub fn load_keystore_from_embedded() {
|
||||
store.vag_keys.len()
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Keystore file loading from ~/.config/KAT/keystore/
|
||||
// =============================================================================
|
||||
|
||||
/// Parse a hex string (with or without "0x" prefix) into a u64.
|
||||
fn parse_hex_u64(s: &str) -> Option<u64> {
|
||||
let s = s.trim();
|
||||
let s = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s);
|
||||
u64::from_str_radix(s, 16).ok()
|
||||
}
|
||||
|
||||
/// Default keystore.ini template content.
|
||||
/// Written to disk on first run so users know which keys to configure.
|
||||
const DEFAULT_KEYSTORE_INI: &str = r#"; KAT Keystore — Protocol Encryption Keys
|
||||
;
|
||||
; Place your protocol keys here. Key values should be in hexadecimal
|
||||
; with a 0x prefix (e.g. 0x0123456789ABCDEF).
|
||||
;
|
||||
; Keys left at 0x0000000000000000 or omitted are treated as "not loaded"
|
||||
; and the corresponding protocol will decode without decryption
|
||||
; (serial/button still visible, but counter may not validate).
|
||||
;
|
||||
; This file corresponds to protopirate's keystore/encrypted and keystore/vag
|
||||
; assets. Since KAT runs on a PC (not a Flipper), keys must be provided
|
||||
; in plaintext here rather than in the Flipper's encrypted keystore format.
|
||||
|
||||
[kia]
|
||||
; KIA V3/V4: KeeLoq manufacturer key (used for hop-code decryption)
|
||||
mf_key = 0x0000000000000000
|
||||
|
||||
; KIA V5: Custom mixer cipher key
|
||||
v5_key = 0x0000000000000000
|
||||
|
||||
; KIA V6: AES-128 key components (XOR-masked before use)
|
||||
v6_a_key = 0x0000000000000000
|
||||
v6_b_key = 0x0000000000000000
|
||||
|
||||
[star_line]
|
||||
; Star Line: KeeLoq manufacturer key (used for hop-code decryption)
|
||||
mf_key = 0x0000000000000000
|
||||
|
||||
[vag]
|
||||
; VAG: Path to AUT64 binary key file (raw packed keys, 16 bytes each).
|
||||
; Can be absolute or relative to the keystore directory.
|
||||
; Leave empty or commented out if you don't have VAG keys.
|
||||
; keys_file = vag.bin
|
||||
"#;
|
||||
|
||||
/// Write the default keystore.ini template if it doesn't exist yet.
|
||||
pub fn create_default_keystore(keystore_dir: &Path) {
|
||||
let ini_path = keystore_dir.join("keystore.ini");
|
||||
if ini_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
match std::fs::write(&ini_path, DEFAULT_KEYSTORE_INI) {
|
||||
Ok(_) => info!("Created default keystore template at {:?}", ini_path),
|
||||
Err(e) => warn!("Could not create default keystore.ini: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load all keys from the keystore directory.
|
||||
///
|
||||
/// Reads `keystore.ini` from the given directory and populates the global
|
||||
/// [`KeyStore`] with:
|
||||
///
|
||||
/// - KIA V3/V4 manufacturer key (`[kia] mf_key`)
|
||||
/// - KIA V5 mixer key (`[kia] v5_key`)
|
||||
/// - KIA V6 AES keys (`[kia] v6_a_key`, `[kia] v6_b_key`)
|
||||
/// - Star Line manufacturer key (`[star_line] mf_key`)
|
||||
/// - VAG AUT64 keys from binary file (`[vag] keys_file`)
|
||||
///
|
||||
/// Keys that are missing, zeroed, or unparseable are silently skipped.
|
||||
pub fn load_keystore_from_dir(keystore_dir: &Path) {
|
||||
let ini_path = keystore_dir.join("keystore.ini");
|
||||
|
||||
if !ini_path.exists() {
|
||||
info!("No keystore.ini found at {:?} — keys not loaded", ini_path);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut ini = Ini::new();
|
||||
if let Err(e) = ini.load(ini_path.to_string_lossy().as_ref()) {
|
||||
error!("Failed to parse keystore.ini: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut store = get_keystore_mut();
|
||||
let mut loaded_count = 0u32;
|
||||
|
||||
// ── KIA keys ──────────────────────────────────────────────────────────
|
||||
if let Some(val) = ini.get("kia", "mf_key").and_then(|s| parse_hex_u64(&s)) {
|
||||
if val != 0 {
|
||||
store.kia_mf_key = val;
|
||||
loaded_count += 1;
|
||||
info!("Loaded KIA V3/V4 manufacturer key");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = ini.get("kia", "v5_key").and_then(|s| parse_hex_u64(&s)) {
|
||||
if val != 0 {
|
||||
store.kia_v5_key = val;
|
||||
loaded_count += 1;
|
||||
info!("Loaded KIA V5 mixer key");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = ini.get("kia", "v6_a_key").and_then(|s| parse_hex_u64(&s)) {
|
||||
if val != 0 {
|
||||
store.kia_v6_a_key = val;
|
||||
loaded_count += 1;
|
||||
info!("Loaded KIA V6 AES key A");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = ini.get("kia", "v6_b_key").and_then(|s| parse_hex_u64(&s)) {
|
||||
if val != 0 {
|
||||
store.kia_v6_b_key = val;
|
||||
loaded_count += 1;
|
||||
info!("Loaded KIA V6 AES key B");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Star Line keys ────────────────────────────────────────────────────
|
||||
if let Some(val) = ini.get("star_line", "mf_key").and_then(|s| parse_hex_u64(&s)) {
|
||||
if val != 0 {
|
||||
store.star_line_mf_key = val;
|
||||
loaded_count += 1;
|
||||
info!("Loaded Star Line manufacturer key");
|
||||
}
|
||||
}
|
||||
|
||||
// ── VAG AUT64 keys (binary file) ──────────────────────────────────────
|
||||
if let Some(vag_file) = ini.get("vag", "keys_file") {
|
||||
let vag_file = vag_file.trim().to_string();
|
||||
if !vag_file.is_empty() {
|
||||
let vag_path = if Path::new(&vag_file).is_absolute() {
|
||||
std::path::PathBuf::from(&vag_file)
|
||||
} else {
|
||||
keystore_dir.join(&vag_file)
|
||||
};
|
||||
|
||||
if vag_path.exists() {
|
||||
match std::fs::read(&vag_path) {
|
||||
Ok(data) => {
|
||||
store.load_vag_keys_from_data(&data);
|
||||
if store.vag_keys_loaded {
|
||||
loaded_count += store.vag_keys.len() as u32;
|
||||
info!("Loaded {} VAG AUT64 keys from {:?}", store.vag_keys.len(), vag_path);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read VAG key file {:?}: {}", vag_path, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("VAG key file not found: {:?}", vag_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if loaded_count > 0 {
|
||||
info!("Keystore loaded: {} key(s) from {:?}", loaded_count, keystore_dir);
|
||||
} else {
|
||||
info!("Keystore loaded but no non-zero keys found. Edit keystore.ini to add your keys.");
|
||||
}
|
||||
}
|
||||
|
||||
+17
-27
@@ -7,7 +7,7 @@
|
||||
//! - Manchester encoding: 800/1600µs timing
|
||||
//! - 57 bits total (32 serial + 8 button + 12 counter + 4 CRC)
|
||||
//! - Long preamble of ~90 long pairs
|
||||
//! - CRC4 checksum with offset rules (cnt_high 0 / >= 6)
|
||||
//! - CRC4 checksum (XOR of nibbles + offset 1, 7 bytes including cnt_high)
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
@@ -66,13 +66,14 @@ impl KiaV1Decoder {
|
||||
(crc.wrapping_add(offset)) & 0x0F
|
||||
}
|
||||
|
||||
/// Manchester state machine
|
||||
fn manchester_advance(&mut self, is_short: bool, is_high: bool) -> Option<bool> {
|
||||
let event = match (is_short, is_high) {
|
||||
(true, false) => 0, // Short Low
|
||||
(true, true) => 1, // Short High
|
||||
(false, false) => 2, // Long Low
|
||||
(false, true) => 3, // Long High
|
||||
/// Manchester state machine (Flipper convention: level ? ShortLow : ShortHigh)
|
||||
fn manchester_advance(&mut self, is_short: bool, level: bool) -> Option<bool> {
|
||||
// C: event = level ? ManchesterEventShortLow : ManchesterEventShortHigh (inverted)
|
||||
let event = match (is_short, level) {
|
||||
(true, true) => 0, // level=true → ShortLow
|
||||
(true, false) => 1, // level=false → ShortHigh
|
||||
(false, true) => 2, // level=true → LongLow
|
||||
(false, false) => 3, // level=false → LongHigh
|
||||
};
|
||||
|
||||
let (new_state, output) = match (self.manchester_state, event) {
|
||||
@@ -115,15 +116,9 @@ impl KiaV1Decoder {
|
||||
char_data[4] = button;
|
||||
char_data[5] = (counter & 0xFF) as u8;
|
||||
|
||||
let crc = if cnt_high == 0 {
|
||||
let offset = if counter >= 0x098 { button } else { 1 };
|
||||
Self::crc4(&char_data[..6], offset)
|
||||
} else if cnt_high >= 0x6 {
|
||||
// CRC4: always 7 bytes with offset 1 (matches updated ProtoPirate kia_v1.c)
|
||||
char_data[6] = cnt_high as u8;
|
||||
Self::crc4(&char_data, 1)
|
||||
} else {
|
||||
Self::crc4(&char_data[..6], 1)
|
||||
};
|
||||
let crc = Self::crc4(&char_data, 1);
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
@@ -194,8 +189,9 @@ impl ProtocolDecoder for KiaV1Decoder {
|
||||
|
||||
if self.header_count > 70 {
|
||||
if !level && is_short && duration_diff!(self.te_last, TE_LONG) < TE_DELTA {
|
||||
self.decode_count_bit = 1;
|
||||
self.decode_data = 1; // Add first bit
|
||||
// C: decode_count_bit=1, then add_bit(1) increments to 2
|
||||
self.decode_count_bit = 2;
|
||||
self.decode_data = 1;
|
||||
self.header_count = 0;
|
||||
self.step = DecoderStep::DecodeData;
|
||||
}
|
||||
@@ -218,7 +214,7 @@ impl ProtocolDecoder for KiaV1Decoder {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.decode_count_bit >= MIN_COUNT_BIT {
|
||||
if self.decode_count_bit == MIN_COUNT_BIT {
|
||||
let result = self.parse_data();
|
||||
self.step = DecoderStep::Reset;
|
||||
return Some(result);
|
||||
@@ -247,15 +243,9 @@ impl ProtocolDecoder for KiaV1Decoder {
|
||||
char_data[4] = button;
|
||||
char_data[5] = (counter & 0xFF) as u8;
|
||||
|
||||
let crc = if cnt_high == 0 {
|
||||
let offset = if counter >= 0x098 { button } else { 1 };
|
||||
Self::crc4(&char_data[..6], offset)
|
||||
} else if cnt_high >= 0x6 {
|
||||
// CRC4: always 7 bytes with offset 1 (matches updated ProtoPirate kia_v1.c)
|
||||
char_data[6] = cnt_high;
|
||||
Self::crc4(&char_data, 1)
|
||||
} else {
|
||||
Self::crc4(&char_data[..6], 1)
|
||||
};
|
||||
let crc = Self::crc4(&char_data, 1);
|
||||
|
||||
// Build data
|
||||
let data: u64 = ((serial as u64) << 24) |
|
||||
|
||||
+16
-15
@@ -57,18 +57,17 @@ impl KiaV2Decoder {
|
||||
}
|
||||
}
|
||||
|
||||
/// CRC4 for Kia V2 (matches kia_v2.c: 6-byte permuted input, XOR nibbles, offset 1)
|
||||
/// CRC4 for Kia V2 (matches kia_v2.c: strip CRC nibble, XOR all remaining nibbles, + offset 1)
|
||||
fn calculate_crc(data: u64) -> u8 {
|
||||
let serial = ((data >> 20) & 0xFFFFFFFF) as u32;
|
||||
let u_var4 = (data & 0xFFFFFFFF) as u32;
|
||||
|
||||
// C code: data_without_crc = data >> 4; then read 6 sequential bytes (bits 4..51)
|
||||
let data_without_crc = data >> 4;
|
||||
let mut bytes = [0u8; 6];
|
||||
bytes[0] = (u_var4 >> 20) as u8;
|
||||
bytes[1] = ((u_var4 >> 28) | ((serial & 0x0F) << 4)) as u8;
|
||||
bytes[2] = (serial >> 4) as u8;
|
||||
bytes[3] = (serial >> 12) as u8;
|
||||
bytes[4] = (u_var4 >> 4) as u8;
|
||||
bytes[5] = (u_var4 >> 12) as u8;
|
||||
bytes[0] = (data_without_crc & 0xFF) as u8;
|
||||
bytes[1] = ((data_without_crc >> 8) & 0xFF) as u8;
|
||||
bytes[2] = ((data_without_crc >> 16) & 0xFF) as u8;
|
||||
bytes[3] = ((data_without_crc >> 24) & 0xFF) as u8;
|
||||
bytes[4] = ((data_without_crc >> 32) & 0xFF) as u8;
|
||||
bytes[5] = ((data_without_crc >> 40) & 0xFF) as u8;
|
||||
|
||||
let mut crc: u8 = 0;
|
||||
for &byte in &bytes {
|
||||
@@ -181,19 +180,21 @@ impl ProtocolDecoder for KiaV2Decoder {
|
||||
self.te_last = duration;
|
||||
self.header_count += 1;
|
||||
} else if is_short && self.header_count >= 100 {
|
||||
// C code: decode_count_bit=1, then add_bit(1) which shifts and increments to 2
|
||||
self.header_count = 0;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 1;
|
||||
self.step = DecoderStep::CollectRawBits;
|
||||
self.decode_data = 1; // First bit
|
||||
self.decode_count_bit = 2;
|
||||
self.step = DecoderStep::CollectRawBits;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.te_last = duration; // C stays in CheckPreamble, updates te_last
|
||||
}
|
||||
} else {
|
||||
if is_long {
|
||||
self.header_count += 1;
|
||||
self.te_last = duration;
|
||||
} else if !is_short {
|
||||
} else if is_short {
|
||||
self.te_last = duration; // C stays in CheckPreamble for short LOW
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
+197
-7
@@ -1,8 +1,8 @@
|
||||
//! Kia V6 protocol decoder (decode-only)
|
||||
//! Kia V6 protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/kia_v6.c`.
|
||||
//! Decode logic (Manchester level mapping, 3-part 144-bit frame, AES-128, CRC8, keystore XOR) matches reference.
|
||||
//! No encoder in protopirate.
|
||||
//! Encoder ported from protopirate (ENABLE_EMULATE_FEATURE): AES-128 encrypt, two-pass Manchester.
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - Manchester encoding: 200/400µs (level convention inverted vs Flipper; see manchester_advance)
|
||||
@@ -275,6 +275,181 @@ impl KiaV6Decoder {
|
||||
*data = state;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Forward AES functions for encoder (matches kia_v6.c ENABLE_EMULATE_FEATURE)
|
||||
// =========================================================================
|
||||
|
||||
/// AES forward SubBytes
|
||||
fn aes_subbytes(state: &mut [u8; 16]) {
|
||||
for i in 0..16 {
|
||||
state[i] = AES_SBOX[state[i] as usize];
|
||||
}
|
||||
}
|
||||
|
||||
/// AES forward ShiftRows
|
||||
fn aes_shiftrows(state: &mut [u8; 16]) {
|
||||
let temp = state[1];
|
||||
state[1] = state[5];
|
||||
state[5] = state[9];
|
||||
state[9] = state[13];
|
||||
state[13] = temp;
|
||||
|
||||
let temp = state[2];
|
||||
state[2] = state[10];
|
||||
state[10] = temp;
|
||||
let temp = state[6];
|
||||
state[6] = state[14];
|
||||
state[14] = temp;
|
||||
|
||||
let temp = state[3];
|
||||
state[3] = state[15];
|
||||
state[15] = state[11];
|
||||
state[11] = state[7];
|
||||
state[7] = temp;
|
||||
}
|
||||
|
||||
/// AES forward MixColumns
|
||||
fn aes_mixcolumns(state: &mut [u8; 16]) {
|
||||
for i in 0..4 {
|
||||
let a = state[i * 4];
|
||||
let b = state[i * 4 + 1];
|
||||
let c = state[i * 4 + 2];
|
||||
let d = state[i * 4 + 3];
|
||||
state[i * 4] = Self::gf_mul2(a) ^ Self::gf_mul2(b) ^ b ^ c ^ d;
|
||||
state[i * 4 + 1] = a ^ Self::gf_mul2(b) ^ Self::gf_mul2(c) ^ c ^ d;
|
||||
state[i * 4 + 2] = a ^ b ^ Self::gf_mul2(c) ^ Self::gf_mul2(d) ^ d;
|
||||
state[i * 4 + 3] = Self::gf_mul2(a) ^ a ^ b ^ c ^ Self::gf_mul2(d);
|
||||
}
|
||||
}
|
||||
|
||||
/// AES-128 encrypt
|
||||
fn aes128_encrypt(expanded_key: &[u8; 176], data: &mut [u8; 16]) {
|
||||
let mut state = *data;
|
||||
|
||||
Self::aes_addroundkey(&mut state, &expanded_key[0..16]);
|
||||
|
||||
for round in 1..10 {
|
||||
Self::aes_subbytes(&mut state);
|
||||
Self::aes_shiftrows(&mut state);
|
||||
Self::aes_mixcolumns(&mut state);
|
||||
Self::aes_addroundkey(&mut state, &expanded_key[round * 16..(round + 1) * 16]);
|
||||
}
|
||||
|
||||
Self::aes_subbytes(&mut state);
|
||||
Self::aes_shiftrows(&mut state);
|
||||
Self::aes_addroundkey(&mut state, &expanded_key[160..176]);
|
||||
|
||||
*data = state;
|
||||
}
|
||||
|
||||
/// Encrypt payload for transmission (matches kia_v6.c kia_v6_encrypt_payload)
|
||||
fn encrypt_payload(
|
||||
fx_field: u8,
|
||||
serial: u32,
|
||||
button: u8,
|
||||
counter: u32,
|
||||
) -> (u32, u32, u32, u32, u16) {
|
||||
let mut plain = [0u8; 16];
|
||||
plain[0] = fx_field;
|
||||
plain[4] = ((serial >> 16) & 0xFF) as u8;
|
||||
plain[5] = ((serial >> 8) & 0xFF) as u8;
|
||||
plain[6] = (serial & 0xFF) as u8;
|
||||
plain[7] = button & 0x0F;
|
||||
plain[8] = ((counter >> 24) & 0xFF) as u8;
|
||||
plain[9] = ((counter >> 16) & 0xFF) as u8;
|
||||
plain[10] = ((counter >> 8) & 0xFF) as u8;
|
||||
plain[11] = (counter & 0xFF) as u8;
|
||||
plain[12] = AES_SBOX[(counter & 0xFF) as usize];
|
||||
plain[15] = Self::crc8(&plain[..15], 0xFF, 0x07);
|
||||
|
||||
let aes_key = Self::get_aes_key();
|
||||
let expanded_key = Self::aes_key_expansion(&aes_key);
|
||||
Self::aes128_encrypt(&expanded_key, &mut plain);
|
||||
|
||||
let fx_hi = 0x20 | (fx_field >> 4);
|
||||
let fx_lo = fx_field & 0x0F;
|
||||
let part1_high = ((fx_hi as u32) << 24)
|
||||
| ((fx_lo as u32) << 16)
|
||||
| ((plain[0] as u32) << 8)
|
||||
| (plain[1] as u32);
|
||||
let part1_low = ((plain[2] as u32) << 24)
|
||||
| ((plain[3] as u32) << 16)
|
||||
| ((plain[4] as u32) << 8)
|
||||
| (plain[5] as u32);
|
||||
let part2_high = ((plain[6] as u32) << 24)
|
||||
| ((plain[7] as u32) << 16)
|
||||
| ((plain[8] as u32) << 8)
|
||||
| (plain[9] as u32);
|
||||
let part2_low = ((plain[10] as u32) << 24)
|
||||
| ((plain[11] as u32) << 16)
|
||||
| ((plain[12] as u32) << 8)
|
||||
| (plain[13] as u32);
|
||||
let part3 = ((plain[14] as u16) << 8) | (plain[15] as u16);
|
||||
|
||||
(part1_low, part1_high, part2_low, part2_high, part3)
|
||||
}
|
||||
|
||||
/// Build encoder signal: two-pass Manchester with preambles (matches kia_v6.c)
|
||||
fn build_upload(
|
||||
p1_lo: u32, p1_hi: u32,
|
||||
p2_lo: u32, p2_hi: u32,
|
||||
p3: u16,
|
||||
) -> Vec<LevelDuration> {
|
||||
let mut signal = Vec::with_capacity(2000);
|
||||
|
||||
// Two passes: 640 preamble pairs, then 38 preamble pairs
|
||||
for &preamble_pairs in &[640u32, 38u32] {
|
||||
// Preamble: short/short pairs
|
||||
for _ in 0..preamble_pairs {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
|
||||
// Sync: short LOW, long HIGH, short LOW
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
|
||||
// Part1: bits 60 down to 0 (61 bits), inverted
|
||||
for b in (0..=60).rev() {
|
||||
let word = if b >= 32 { p1_hi } else { p1_lo };
|
||||
let shift = if b >= 32 { b - 32 } else { b };
|
||||
let bit = ((!word) >> shift) & 1 == 1;
|
||||
Self::encode_manchester_bit(&mut signal, bit);
|
||||
}
|
||||
|
||||
// Part2: bits 63 down to 0 (64 bits), inverted
|
||||
for b in (0..=63).rev() {
|
||||
let word = if b >= 32 { p2_hi } else { p2_lo };
|
||||
let shift = if b >= 32 { b - 32 } else { b };
|
||||
let bit = ((!word) >> shift) & 1 == 1;
|
||||
Self::encode_manchester_bit(&mut signal, bit);
|
||||
}
|
||||
|
||||
// Part3: bits 15 down to 0 (16 bits), inverted
|
||||
for b in (0..=15).rev() {
|
||||
let bit = ((!p3) >> b) & 1 == 1;
|
||||
Self::encode_manchester_bit(&mut signal, bit);
|
||||
}
|
||||
|
||||
// Gap between passes
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
}
|
||||
|
||||
signal
|
||||
}
|
||||
|
||||
/// Encode one Manchester bit (matches kia_v6.c kia_v6_encode_manchester_bit)
|
||||
fn encode_manchester_bit(signal: &mut Vec<LevelDuration>, bit: bool) {
|
||||
if bit {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
}
|
||||
|
||||
/// AES-128 key from V6 keystores A+B with XOR_MASK_LOW/HIGH (matches kia_v6.c)
|
||||
fn get_aes_key() -> [u8; 16] {
|
||||
let keystore_a = Self::get_keystore_a();
|
||||
@@ -306,6 +481,13 @@ impl KiaV6Decoder {
|
||||
aes_key
|
||||
}
|
||||
|
||||
/// Extract fx_field from stored_part1_high (matches kia_v6.c fx_field extraction)
|
||||
fn extract_fx_field(&self) -> u8 {
|
||||
let fx_byte0 = ((self.stored_part1_high >> 24) & 0xFF) as u8;
|
||||
let fx_byte1 = ((self.stored_part1_high >> 16) & 0xFF) as u8;
|
||||
((fx_byte0 & 0xF) << 4) | (fx_byte1 & 0xF)
|
||||
}
|
||||
|
||||
/// Decrypt 16-byte block: byte layout matches kia_v6.c; AES-128 then CRC8 check
|
||||
fn decrypt(&self) -> Option<(u32, u8, u32, bool)> {
|
||||
let mut encrypted_data = [0u8; 16];
|
||||
@@ -528,6 +710,7 @@ impl ProtocolDecoder for KiaV6Decoder {
|
||||
if let Some((serial, button, counter, crc_valid)) = self.decrypt() {
|
||||
let key_data = ((self.stored_part1_high as u64) << 32) |
|
||||
(self.stored_part1_low as u64);
|
||||
let fx_field = self.extract_fx_field();
|
||||
|
||||
self.step = DecoderStep::Reset;
|
||||
return Some(DecodedSignal {
|
||||
@@ -537,8 +720,8 @@ impl ProtocolDecoder for KiaV6Decoder {
|
||||
crc_valid,
|
||||
data: key_data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
encoder_capable: true,
|
||||
extra: Some(fx_field as u64),
|
||||
protocol_display_name: None,
|
||||
});
|
||||
}
|
||||
@@ -552,11 +735,18 @@ impl ProtocolDecoder for KiaV6Decoder {
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false // V6 is decode-only
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None // V6 decode-only in protopirate
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let serial = decoded.serial?;
|
||||
let counter = decoded.counter.unwrap_or(0) as u32;
|
||||
let fx_field = decoded.extra.unwrap_or(0) as u8;
|
||||
|
||||
let (p1_lo, p1_hi, p2_lo, p2_hi, p3) =
|
||||
Self::encrypt_payload(fx_field, serial, button, counter);
|
||||
|
||||
Some(Self::build_upload(p1_lo, p1_hi, p2_lo, p2_hi, p3))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
//! Kia V7 protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/kia_v7.c` and `kia_v7.h`.
|
||||
//! Manchester 250/500µs, 64 bits, FM. The decoded 64-bit word is bit-inverted (`~data`); a valid
|
||||
//! frame has a fixed high byte 0x4C and a CRC8 (poly 0x7F, init 0x4C) over bytes 0..7. Emission is
|
||||
//! gated on header + CRC, so Kia V7 is strongly validated and will not false-match.
|
||||
//!
|
||||
//! Decoder steps: Reset → Preamble (short pairs, ≥16) → SyncLow → Data. The preamble→sync transition
|
||||
//! preloads four seed bits (1,0,1,1 = the inverted header's top nibble 0xB) before collecting the
|
||||
//! remaining 60 Manchester bits. Encoder supported.
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use super::common::crc8;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 100;
|
||||
const KEY_BITS: usize = 64;
|
||||
const HEADER: u8 = 0x4C;
|
||||
const PREAMBLE_MIN_PAIRS: u16 = 16;
|
||||
const TAIL_GAP_US: u32 = 2000;
|
||||
const TX_PREAMBLE_PAIRS: usize = 32;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0 = 0,
|
||||
Mid1 = 1,
|
||||
Start0 = 2,
|
||||
Start1 = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Preamble,
|
||||
SyncLow,
|
||||
Data,
|
||||
}
|
||||
|
||||
pub struct KiaV7Decoder {
|
||||
step: DecoderStep,
|
||||
manchester_state: ManchesterState,
|
||||
te_last: u32,
|
||||
preamble_count: u16,
|
||||
decode_data: u64,
|
||||
decode_count_bit: usize,
|
||||
}
|
||||
|
||||
impl KiaV7Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
te_last: 0,
|
||||
preamble_count: 0,
|
||||
decode_data: 0,
|
||||
decode_count_bit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_short(d: u32) -> bool {
|
||||
duration_diff!(d, TE_SHORT) < TE_DELTA
|
||||
}
|
||||
fn is_long(d: u32) -> bool {
|
||||
duration_diff!(d, TE_LONG) < TE_DELTA
|
||||
}
|
||||
|
||||
fn add_bit(&mut self, bit: bool) {
|
||||
self.decode_data = (self.decode_data << 1) | (bit as u64);
|
||||
self.decode_count_bit += 1;
|
||||
}
|
||||
|
||||
fn manchester_advance(&mut self, event: u8) -> Option<bool> {
|
||||
let (new_state, emit) = match (self.manchester_state, event) {
|
||||
(ManchesterState::Mid0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 1) => (ManchesterState::Start1, true),
|
||||
(ManchesterState::Mid0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Mid0, 3) => (ManchesterState::Mid1, true),
|
||||
|
||||
(ManchesterState::Mid1, 0) => (ManchesterState::Start0, true),
|
||||
(ManchesterState::Mid1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Mid1, 2) => (ManchesterState::Mid0, true),
|
||||
(ManchesterState::Mid1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 1) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start0, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
(ManchesterState::Start1, 0) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, false),
|
||||
(ManchesterState::Start1, 2) => (ManchesterState::Mid0, false),
|
||||
(ManchesterState::Start1, 3) => (ManchesterState::Mid1, false),
|
||||
|
||||
_ => (ManchesterState::Mid1, false),
|
||||
};
|
||||
self.manchester_state = new_state;
|
||||
if emit { Some((event & 1) == 1) } else { None }
|
||||
}
|
||||
|
||||
/// Decode the 8 plaintext bytes of the (already inverted) key into fields.
|
||||
/// Returns (serial, button, counter, crc_valid).
|
||||
fn decode_key(data: u64) -> (u32, u8, u16, bool) {
|
||||
let bytes = data.to_be_bytes();
|
||||
let serial = (((bytes[3] as u32) << 20)
|
||||
| ((bytes[4] as u32) << 12)
|
||||
| ((bytes[5] as u32) << 4)
|
||||
| ((bytes[6] as u32) >> 4))
|
||||
& 0x0FFF_FFFF;
|
||||
let counter = ((bytes[1] as u16) << 8) | bytes[2] as u16;
|
||||
let button = bytes[6] & 0x0F;
|
||||
let crc_calc = crc8(&bytes[0..7], 0x7F, 0x4C);
|
||||
let crc_valid = crc_calc == bytes[7];
|
||||
(serial, button, counter, crc_valid)
|
||||
}
|
||||
|
||||
/// Rebuild the 64-bit key from fields (matches kia_v7_encode_key).
|
||||
fn encode_key(serial: u32, button: u8, counter: u16) -> u64 {
|
||||
let serial = serial & 0x0FFF_FFFF;
|
||||
let button = button & 0x0F;
|
||||
let mut bytes = [0u8; 8];
|
||||
bytes[0] = HEADER;
|
||||
bytes[1] = (counter >> 8) as u8;
|
||||
bytes[2] = counter as u8;
|
||||
bytes[3] = (serial >> 20) as u8;
|
||||
bytes[4] = (serial >> 12) as u8;
|
||||
bytes[5] = (serial >> 4) as u8;
|
||||
bytes[6] = (((serial & 0x0F) as u8) << 4) | button;
|
||||
bytes[7] = crc8(&bytes[0..7], 0x7F, 0x4C);
|
||||
u64::from_be_bytes(bytes)
|
||||
}
|
||||
|
||||
/// Map KAT generic button command to a Kia V7 4-bit button code.
|
||||
fn map_button(button: u8) -> u8 {
|
||||
match button {
|
||||
0x01 => 0x01, // Lock
|
||||
0x02 => 0x02, // Unlock
|
||||
0x04 => 0x03, // Trunk
|
||||
0x08 => 0x08, // Trunk/aux
|
||||
b => b & 0x0F,
|
||||
}
|
||||
}
|
||||
|
||||
fn enc_add_level(signal: &mut Vec<LevelDuration>, level: bool, duration: u32) {
|
||||
if let Some(last) = signal.last_mut() {
|
||||
if last.level == level {
|
||||
*last = LevelDuration::new(level, last.duration_us + duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
signal.push(LevelDuration::new(level, duration));
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for KiaV7Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Kia V7"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: KEY_BITS,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[315_000_000, 433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
self.te_last = 0;
|
||||
self.preamble_count = 0;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if level && Self::is_short(duration) {
|
||||
self.step = DecoderStep::Preamble;
|
||||
self.te_last = duration;
|
||||
self.preamble_count = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Preamble => {
|
||||
if level {
|
||||
if Self::is_long(duration) && Self::is_short(self.te_last) {
|
||||
if self.preamble_count > (PREAMBLE_MIN_PAIRS - 1) {
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.preamble_count = 0;
|
||||
// Seed the inverted-header top nibble (1,0,1,1).
|
||||
self.add_bit(true);
|
||||
self.add_bit(false);
|
||||
self.add_bit(true);
|
||||
self.add_bit(true);
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::SyncLow;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else if Self::is_short(duration) {
|
||||
self.te_last = duration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else if Self::is_short(duration) && Self::is_short(self.te_last) {
|
||||
self.preamble_count += 1;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::SyncLow => {
|
||||
if !level && Self::is_short(duration) && Self::is_long(self.te_last) {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::Data;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Data => {
|
||||
let event = if Self::is_short(duration) {
|
||||
Some(if level { 1 } else { 0 })
|
||||
} else if Self::is_long(duration) {
|
||||
Some(if level { 3 } else { 2 })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(ev) = event {
|
||||
if let Some(bit) = self.manchester_advance(ev) {
|
||||
self.add_bit(bit);
|
||||
}
|
||||
}
|
||||
|
||||
if self.decode_count_bit == KEY_BITS {
|
||||
let candidate = !self.decode_data;
|
||||
let hdr = (candidate >> 56) as u8;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.step = DecoderStep::Reset;
|
||||
|
||||
if hdr == HEADER {
|
||||
let (serial, button, counter, crc_valid) = Self::decode_key(candidate);
|
||||
if crc_valid {
|
||||
return Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid: true,
|
||||
data: candidate,
|
||||
data_count_bit: KEY_BITS,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let serial = decoded.serial?;
|
||||
let counter = decoded.counter.unwrap_or(0);
|
||||
let key = Self::encode_key(serial, Self::map_button(button), counter);
|
||||
|
||||
let mut signal = Vec::with_capacity(TX_PREAMBLE_PAIRS * 2 + KEY_BITS * 2 + 4);
|
||||
// Preamble: alternating short pulses.
|
||||
for _ in 0..TX_PREAMBLE_PAIRS {
|
||||
Self::enc_add_level(&mut signal, true, TE_SHORT);
|
||||
Self::enc_add_level(&mut signal, false, TE_SHORT);
|
||||
}
|
||||
// Standalone high short (merges with first data-bit high to form the long sync pulse).
|
||||
Self::enc_add_level(&mut signal, true, TE_SHORT);
|
||||
// Manchester data, MSB first: bit 1 → (H,L), bit 0 → (L,H).
|
||||
for bit in (0..KEY_BITS).rev() {
|
||||
let value = (key >> bit) & 1 != 0;
|
||||
if value {
|
||||
Self::enc_add_level(&mut signal, true, TE_SHORT);
|
||||
Self::enc_add_level(&mut signal, false, TE_SHORT);
|
||||
} else {
|
||||
Self::enc_add_level(&mut signal, false, TE_SHORT);
|
||||
Self::enc_add_level(&mut signal, true, TE_SHORT);
|
||||
}
|
||||
}
|
||||
// Trailing high short + tail gap.
|
||||
Self::enc_add_level(&mut signal, true, TE_SHORT);
|
||||
Self::enc_add_level(&mut signal, false, TAIL_GAP_US);
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for KiaV7Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
//! Land Rover RKE protocol decoder/encoder
|
||||
//!
|
||||
//! Ported from the Flipper-ARF firmware (`lib/subghz/protocols/landrover_rke.c` / `.h`,
|
||||
//! D4C1-Labs), itself derived from Pandora DXL 5000 firmware. Land Rover shares the
|
||||
//! Ford/Jaguar baseband (firmware protocol ID 0x0E) but uses a distinct 66-bit frame.
|
||||
//!
|
||||
//! Encoding: fixed-width PWM, OOK/AM carrier. Bit period 1000µs:
|
||||
//! Bit-1 = 700µs HIGH + 300µs LOW; Bit-0 = 300µs HIGH + 700µs LOW.
|
||||
//! Preamble = 20× (400µs HIGH + 600µs LOW); sync = 400µs HIGH + 9600µs LOW.
|
||||
//! Tolerance ±20% (relative), matching the C `lr_in_range`.
|
||||
//!
|
||||
//! Frame (66 bits, MSB-first), matching the C layout:
|
||||
//! [65:34] 32-bit KeeLoq encrypted hopping code
|
||||
//! [33:10] 24-bit fixed fob serial
|
||||
//! [9:6] 4-bit button code (0x1=Lock, 0x2=Unlock, 0x4=Boot/Tailgate, 0x8=Panic)
|
||||
//! [5:2] 4-bit function/repeat flags
|
||||
//! [1:0] 2-bit status (0x1=battery low, 0x2=repeat)
|
||||
//!
|
||||
//! KeeLoq: the hop code is the raw 32-bit KeeLoq ciphertext. Full decryption needs the
|
||||
//! per-fob manufacturer key (provisioned, not in the firmware), so KAT exposes the framed
|
||||
//! fields (serial/button) and leaves the hop encrypted — `crc_valid=false` since no real
|
||||
//! cryptographic check is performed. Emission is still gated tightly on the structural
|
||||
//! invariants below so this loose-looking PWM decoder does not false-match
|
||||
//! Kia/Subaru/Ford captures.
|
||||
//!
|
||||
//! Storage: 66 bits do not fit a u64. The canonical `DecodedSignal.data` holds the low 64
|
||||
//! frame bits and `DecodedSignal.extra` holds the top 2 (frame bits [65:64], the high 2 bits
|
||||
//! of the hop code), so encode() round-trips the hop code losslessly. `data_count_bit` = 66.
|
||||
//!
|
||||
//! Gating: a valid frame requires a long preamble run (≥16 of the 400/600µs pairs), the very
|
||||
//! distinctive 400µs-HIGH + 9600µs-LOW sync gap, then exactly 66 bits whose HIGH/LOW halves
|
||||
//! each fall inside the ±20% PWM windows. The 9.6ms sync gap + 66-bit PWM payload combination
|
||||
//! is unique among KAT's protocols, so nothing else in the sweep matches it.
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
// Timing constants (microseconds) — verbatim from landrover_rke.h.
|
||||
const PREAMBLE_HIGH_US: u32 = 400;
|
||||
const PREAMBLE_LOW_US: u32 = 600;
|
||||
const PREAMBLE_COUNT: u32 = 20;
|
||||
const SYNC_HIGH_US: u32 = 400;
|
||||
const SYNC_LOW_US: u32 = 9600;
|
||||
const BIT1_HIGH_US: u32 = 700;
|
||||
const BIT1_LOW_US: u32 = 300;
|
||||
const BIT0_HIGH_US: u32 = 300;
|
||||
const BIT0_LOW_US: u32 = 700;
|
||||
const REPEAT_GAP_US: u32 = 12000;
|
||||
const REPEAT_COUNT: u32 = 4;
|
||||
const TOLERANCE_PCT: u32 = 20;
|
||||
const FRAME_BITS: usize = 66;
|
||||
|
||||
// Require a substantial preamble run before accepting the sync gap. The C scans a raw buffer
|
||||
// for the sync directly, but gating on the preamble too makes the streaming decoder specific.
|
||||
const PREAMBLE_MIN_PAIRS: u32 = 16;
|
||||
|
||||
// Button codes (frame bits [9:6]).
|
||||
const BTN_LOCK: u8 = 0x1;
|
||||
const BTN_UNLOCK: u8 = 0x2;
|
||||
const BTN_BOOT: u8 = 0x4;
|
||||
const BTN_PANIC: u8 = 0x8;
|
||||
|
||||
/// Relative-tolerance match, matching the C `lr_in_range`:
|
||||
/// `|measured - ref| * 100 <= ref * TOLERANCE_PCT`.
|
||||
#[inline]
|
||||
fn in_range(measured_us: u32, ref_us: u32) -> bool {
|
||||
let diff = if measured_us > ref_us {
|
||||
measured_us - ref_us
|
||||
} else {
|
||||
ref_us - measured_us
|
||||
};
|
||||
diff.saturating_mul(100) <= ref_us.saturating_mul(TOLERANCE_PCT)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
/// Looking for the first preamble HIGH pulse.
|
||||
Reset,
|
||||
/// Counting preamble (400µs HIGH + 600µs LOW) pairs; te_last holds the pending HIGH.
|
||||
Preamble,
|
||||
/// Sync seen — expecting the next data-bit HIGH half.
|
||||
DataHigh,
|
||||
/// Collected a data-bit HIGH in te_last; decide the bit value from the following LOW.
|
||||
DataLow,
|
||||
}
|
||||
|
||||
/// Land Rover RKE protocol decoder.
|
||||
///
|
||||
/// Bits are accumulated MSB-first into a 66-element array (index 0 = first bit on air =
|
||||
/// frame bit 65), mirroring the C `bits[65 - b]` indexing and avoiding any u64 overflow.
|
||||
pub struct LandRoverRkeDecoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
preamble_count: u32,
|
||||
/// Received bits, MSB-first: `rx_bits[i]` is the (i+1)-th bit on air = frame bit `65 - i`.
|
||||
rx_bits: [u8; FRAME_BITS],
|
||||
decode_count_bit: usize,
|
||||
}
|
||||
|
||||
impl LandRoverRkeDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
preamble_count: 0,
|
||||
rx_bits: [0u8; FRAME_BITS],
|
||||
decode_count_bit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a KAT generic button command to a Land Rover RKE 4-bit button code.
|
||||
/// KAT: Lock=0x01, Unlock=0x02, Trunk=0x04, Panic=0x08.
|
||||
/// LR RKE bits [9:6]: Lock=0x1, Unlock=0x2, Boot/Tailgate=0x4, Panic=0x8 — identical mapping.
|
||||
fn map_button(button: u8) -> u8 {
|
||||
match button {
|
||||
0x01 => BTN_LOCK,
|
||||
0x02 => BTN_UNLOCK,
|
||||
0x04 => BTN_BOOT,
|
||||
0x08 => BTN_PANIC,
|
||||
b => b & 0x0F,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the logical 66-bit frame array from the field values.
|
||||
///
|
||||
/// The array is indexed by frame bit number (bit 0 = LSB of the whole 66-bit frame). Each
|
||||
/// field `X` occupying frame bits `[hi:lo]` maps frame bit `(lo + j)` = field bit `j`, so the
|
||||
/// field's MSB lands at the higher frame-bit index. The encoder transmits frame bit 65 first,
|
||||
/// giving the documented MSB-first-on-air order. This is the exact inverse of `unpack_frame`.
|
||||
///
|
||||
/// NOTE: the Flipper-ARF C reference's `lr_encode`/`lr_decode` use inconsistent bit
|
||||
/// endianness for the fields (encode writes `bits[65-i]=field>>i`, decode reads
|
||||
/// `field|=bits[65-k]<<(31-k)`), so the C does not round-trip. KAT preserves the documented
|
||||
/// field *layout* and MSB-first wire order while keeping encode/decode mutually consistent.
|
||||
fn pack_frame(hop_code: u32, serial: u32, button: u8, func_bits: u8, status: u8) -> [u8; FRAME_BITS] {
|
||||
let mut bits = [0u8; FRAME_BITS];
|
||||
// hop_code: frame bits [65:34] (32 bits).
|
||||
for j in 0..32 {
|
||||
bits[34 + j] = ((hop_code >> j) & 1) as u8;
|
||||
}
|
||||
// serial: frame bits [33:10] (24 bits).
|
||||
for j in 0..24 {
|
||||
bits[10 + j] = ((serial >> j) & 1) as u8;
|
||||
}
|
||||
// button: frame bits [9:6] (4 bits).
|
||||
for j in 0..4 {
|
||||
bits[6 + j] = ((button >> j) & 1) as u8;
|
||||
}
|
||||
// func_bits: frame bits [5:2] (4 bits).
|
||||
for j in 0..4 {
|
||||
bits[2 + j] = ((func_bits >> j) & 1) as u8;
|
||||
}
|
||||
// status: frame bits [1:0] (2 bits).
|
||||
bits[0] = status & 1;
|
||||
bits[1] = (status >> 1) & 1;
|
||||
bits
|
||||
}
|
||||
|
||||
/// Extract fields from a logical 66-bit frame array (index = frame bit, bit 0 = LSB).
|
||||
/// Returns (hop_code, serial, button, func_bits, status). Exact inverse of `pack_frame`.
|
||||
fn unpack_frame(bits: &[u8; FRAME_BITS]) -> (u32, u32, u8, u8, u8) {
|
||||
let mut hop_code: u32 = 0;
|
||||
for j in 0..32 {
|
||||
hop_code |= (bits[34 + j] as u32) << j;
|
||||
}
|
||||
let mut serial: u32 = 0;
|
||||
for j in 0..24 {
|
||||
serial |= (bits[10 + j] as u32) << j;
|
||||
}
|
||||
let mut button: u8 = 0;
|
||||
for j in 0..4 {
|
||||
button |= bits[6 + j] << j;
|
||||
}
|
||||
let mut func_bits: u8 = 0;
|
||||
for j in 0..4 {
|
||||
func_bits |= bits[2 + j] << j;
|
||||
}
|
||||
let status = bits[0] | (bits[1] << 1);
|
||||
(hop_code, serial, button, func_bits, status)
|
||||
}
|
||||
|
||||
/// Pack a logical 66-bit frame array into `(data, extra)`:
|
||||
/// `data` = frame bits [63:0], `extra` = frame bits [65:64].
|
||||
fn frame_to_data(bits: &[u8; FRAME_BITS]) -> (u64, u64) {
|
||||
let mut data: u64 = 0;
|
||||
for i in 0..64 {
|
||||
data |= (bits[i] as u64) << i;
|
||||
}
|
||||
let extra: u64 = (bits[64] as u64) | ((bits[65] as u64) << 1);
|
||||
(data, extra)
|
||||
}
|
||||
|
||||
/// Inverse of `frame_to_data`.
|
||||
fn data_to_frame(data: u64, extra: u64) -> [u8; FRAME_BITS] {
|
||||
let mut bits = [0u8; FRAME_BITS];
|
||||
for i in 0..64 {
|
||||
bits[i] = ((data >> i) & 1) as u8;
|
||||
}
|
||||
bits[64] = (extra & 1) as u8;
|
||||
bits[65] = ((extra >> 1) & 1) as u8;
|
||||
bits
|
||||
}
|
||||
|
||||
/// Convert the received MSB-first `rx_bits` (index 0 = frame bit 65) into the logical
|
||||
/// frame array (index = frame bit number).
|
||||
fn rx_to_frame(rx_bits: &[u8; FRAME_BITS]) -> [u8; FRAME_BITS] {
|
||||
let mut frame = [0u8; FRAME_BITS];
|
||||
for i in 0..FRAME_BITS {
|
||||
frame[65 - i] = rx_bits[i];
|
||||
}
|
||||
frame
|
||||
}
|
||||
|
||||
/// Build a DecodedSignal from a completed received-bit array.
|
||||
fn build_signal(rx_bits: &[u8; FRAME_BITS]) -> DecodedSignal {
|
||||
let frame = Self::rx_to_frame(rx_bits);
|
||||
let (hop_code, serial, button, _func_bits, _status) = Self::unpack_frame(&frame);
|
||||
let (data, extra) = Self::frame_to_data(&frame);
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
// The 16-bit KeeLoq counter is inside the *encrypted* hop code; without the
|
||||
// manufacturer key we cannot recover it. Surface the low 16 bits of the hop
|
||||
// ciphertext so the UI has a stable per-press value.
|
||||
counter: Some((hop_code & 0xFFFF) as u16),
|
||||
// No cryptographic check is performed (no key), so this is not a verified frame.
|
||||
crc_valid: false,
|
||||
data,
|
||||
data_count_bit: FRAME_BITS,
|
||||
encoder_capable: true,
|
||||
// Carry the top 2 frame bits so encode() can faithfully round-trip the hop code.
|
||||
extra: Some(extra),
|
||||
protocol_display_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn push_pair(signal: &mut Vec<LevelDuration>, high_us: u32, low_us: u32) {
|
||||
signal.push(LevelDuration::new(true, high_us));
|
||||
signal.push(LevelDuration::new(false, low_us));
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for LandRoverRkeDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Land Rover RKE"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: BIT0_HIGH_US, // 300µs
|
||||
te_long: BIT1_HIGH_US, // 700µs
|
||||
te_delta: 140, // ~20% of the 700µs long half
|
||||
min_count_bit: FRAME_BITS,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[433_920_000, 315_000_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.te_last = 0;
|
||||
self.preamble_count = 0;
|
||||
self.rx_bits = [0u8; FRAME_BITS];
|
||||
self.decode_count_bit = 0;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
// First preamble HIGH (~400µs).
|
||||
if level && in_range(duration, PREAMBLE_HIGH_US) {
|
||||
self.step = DecoderStep::Preamble;
|
||||
self.te_last = duration;
|
||||
self.preamble_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Preamble => {
|
||||
if level {
|
||||
// A HIGH while in preamble: either another preamble HIGH (~400µs) or a sync
|
||||
// HIGH (also ~400µs) — disambiguated by the LOW that follows.
|
||||
if in_range(duration, PREAMBLE_HIGH_US) {
|
||||
self.te_last = duration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
// LOW following a preamble/sync HIGH.
|
||||
if in_range(self.te_last, SYNC_HIGH_US) && in_range(duration, SYNC_LOW_US) {
|
||||
// Sync gap (~9600µs LOW). Require enough preamble first.
|
||||
if self.preamble_count >= PREAMBLE_MIN_PAIRS {
|
||||
self.rx_bits = [0u8; FRAME_BITS];
|
||||
self.decode_count_bit = 0;
|
||||
self.step = DecoderStep::DataHigh;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else if in_range(self.te_last, PREAMBLE_HIGH_US)
|
||||
&& in_range(duration, PREAMBLE_LOW_US)
|
||||
{
|
||||
// Another preamble pair (~400µs HIGH + ~600µs LOW).
|
||||
self.preamble_count = self.preamble_count.saturating_add(1);
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::DataHigh => {
|
||||
if level {
|
||||
// Data-bit HIGH half — must match a Bit-1 or Bit-0 HIGH window.
|
||||
if in_range(duration, BIT1_HIGH_US) || in_range(duration, BIT0_HIGH_US) {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::DataLow;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::DataLow => {
|
||||
if !level {
|
||||
let hi = self.te_last;
|
||||
let lo = duration;
|
||||
let bit = if in_range(hi, BIT1_HIGH_US) && in_range(lo, BIT1_LOW_US) {
|
||||
Some(1u8)
|
||||
} else if in_range(hi, BIT0_HIGH_US) && in_range(lo, BIT0_LOW_US) {
|
||||
Some(0u8)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match bit {
|
||||
Some(b) => {
|
||||
// Store MSB-first: first bit on air → rx_bits[0] (= frame bit 65).
|
||||
if self.decode_count_bit < FRAME_BITS {
|
||||
self.rx_bits[self.decode_count_bit] = b;
|
||||
}
|
||||
self.decode_count_bit += 1;
|
||||
|
||||
if self.decode_count_bit == FRAME_BITS {
|
||||
let result = Self::build_signal(&self.rx_bits);
|
||||
self.reset();
|
||||
return Some(result);
|
||||
}
|
||||
self.step = DecoderStep::DataHigh;
|
||||
}
|
||||
None => {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let serial = decoded.serial?;
|
||||
// Reconstruct the original frame to preserve the hop code, func bits, and status, then
|
||||
// override only the button. The hop code lives in (data, extra); recover it.
|
||||
let extra = decoded.extra.unwrap_or(0);
|
||||
let orig_frame = Self::data_to_frame(decoded.data, extra);
|
||||
let (hop_code, _serial, _orig_button, func_bits, status) = Self::unpack_frame(&orig_frame);
|
||||
|
||||
let btn = Self::map_button(button);
|
||||
let frame = Self::pack_frame(hop_code, serial, btn, func_bits, status);
|
||||
|
||||
let mut signal = Vec::with_capacity(
|
||||
((PREAMBLE_COUNT as usize + 1 + FRAME_BITS) * 2 + 1) * REPEAT_COUNT as usize,
|
||||
);
|
||||
|
||||
for rep in 0..REPEAT_COUNT {
|
||||
// Preamble: 20 pairs.
|
||||
for _ in 0..PREAMBLE_COUNT {
|
||||
Self::push_pair(&mut signal, PREAMBLE_HIGH_US, PREAMBLE_LOW_US);
|
||||
}
|
||||
// Sync.
|
||||
Self::push_pair(&mut signal, SYNC_HIGH_US, SYNC_LOW_US);
|
||||
// Data bits, MSB-first (frame bit 65 first on air).
|
||||
for b in (0..FRAME_BITS).rev() {
|
||||
if frame[b] != 0 {
|
||||
Self::push_pair(&mut signal, BIT1_HIGH_US, BIT1_LOW_US);
|
||||
} else {
|
||||
Self::push_pair(&mut signal, BIT0_HIGH_US, BIT0_LOW_US);
|
||||
}
|
||||
}
|
||||
// Inter-repetition gap.
|
||||
if rep < REPEAT_COUNT - 1 {
|
||||
signal.push(LevelDuration::new(false, REPEAT_GAP_US));
|
||||
}
|
||||
}
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LandRoverRkeDecoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Feed an encoded signal back through a fresh decoder and return the first decode.
|
||||
fn decode_signal(signal: &[LevelDuration]) -> Option<DecodedSignal> {
|
||||
let mut dec = LandRoverRkeDecoder::new();
|
||||
for ld in signal {
|
||||
if let Some(d) = dec.feed(ld.level, ld.duration_us) {
|
||||
return Some(d);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Build a DecodedSignal seed carrying a chosen hop code + serial (button overridden at encode).
|
||||
fn seed(hop_code: u32, serial: u32, func_bits: u8, status: u8, button: u8) -> DecodedSignal {
|
||||
let frame = LandRoverRkeDecoder::pack_frame(hop_code, serial & 0x00FF_FFFF, button, func_bits, status);
|
||||
let (data, extra) = LandRoverRkeDecoder::frame_to_data(&frame);
|
||||
DecodedSignal {
|
||||
serial: Some(serial & 0x00FF_FFFF),
|
||||
button: Some(button),
|
||||
counter: Some((hop_code & 0xFFFF) as u16),
|
||||
crc_valid: false,
|
||||
data,
|
||||
data_count_bit: FRAME_BITS,
|
||||
encoder_capable: true,
|
||||
extra: Some(extra),
|
||||
protocol_display_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_unpack_roundtrip() {
|
||||
// Field packing/unpacking is exact across the full 66-bit layout.
|
||||
let cases = [
|
||||
(0xDEAD_BEEFu32, 0x00AB_CDEFu32, 0x1u8, BTN_UNLOCK, 0x5u8, 0x2u8),
|
||||
(0x0000_0000, 0x0000_0000, 0x0, 0x0, 0x0, 0x0),
|
||||
(0xFFFF_FFFF, 0x00FF_FFFF, 0xF, 0xF, 0xF, 0x3),
|
||||
(0x1234_5678, 0x0055_AA55, 0x4, BTN_LOCK, 0xA, 0x1),
|
||||
];
|
||||
for &(hop, serial, _btn_field_unused, button, func, status) in &cases {
|
||||
let frame = LandRoverRkeDecoder::pack_frame(hop, serial, button, func, status);
|
||||
let (h, s, b, f, st) = LandRoverRkeDecoder::unpack_frame(&frame);
|
||||
assert_eq!(h, hop, "hop_code mismatch");
|
||||
assert_eq!(s, serial, "serial mismatch");
|
||||
assert_eq!(b, button, "button mismatch");
|
||||
assert_eq!(f, func, "func_bits mismatch");
|
||||
assert_eq!(st, status, "status mismatch");
|
||||
|
||||
// (data, extra) <-> frame is lossless too.
|
||||
let (data, extra) = LandRoverRkeDecoder::frame_to_data(&frame);
|
||||
let frame2 = LandRoverRkeDecoder::data_to_frame(data, extra);
|
||||
assert_eq!(frame, frame2, "data/extra round-trip mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_roundtrip_multiple() {
|
||||
// Multiple serials / hop codes (counters) / buttons all round-trip through encode→decode.
|
||||
let serials = [0x00ABCDEFu32, 0x00123456, 0x00000001, 0x00FFFFFE, 0x005A5A5A];
|
||||
let hops = [0xDEADBEEFu32, 0x00000000, 0xFFFFFFFF, 0x12345678, 0xCAFEBABE];
|
||||
let buttons = [0x01u8, 0x02, 0x04, 0x08]; // Lock, Unlock, Trunk/Boot, Panic
|
||||
|
||||
for (&serial, &hop) in serials.iter().zip(hops.iter()) {
|
||||
for &btn in &buttons {
|
||||
let s = seed(hop, serial, 0x3, 0x1, 0x00); // func/status preserved from seed
|
||||
let encoder = LandRoverRkeDecoder::new();
|
||||
let signal = encoder.encode(&s, btn).expect("encode should succeed");
|
||||
let decoded = decode_signal(&signal)
|
||||
.unwrap_or_else(|| panic!("decode failed for serial {serial:#X} hop {hop:#X} btn {btn:#X}"));
|
||||
|
||||
let expected_btn = LandRoverRkeDecoder::map_button(btn);
|
||||
assert_eq!(decoded.serial, Some(serial & 0x00FF_FFFF), "serial");
|
||||
assert_eq!(decoded.button, Some(expected_btn), "button");
|
||||
// hop code survives via data+extra.
|
||||
let frame = LandRoverRkeDecoder::data_to_frame(decoded.data, decoded.extra.unwrap());
|
||||
let (dec_hop, _, _, dec_func, dec_status) = LandRoverRkeDecoder::unpack_frame(&frame);
|
||||
assert_eq!(dec_hop, hop, "hop_code");
|
||||
assert_eq!(dec_func, 0x3, "func_bits preserved");
|
||||
assert_eq!(dec_status, 0x1, "status preserved");
|
||||
assert_eq!(decoded.data_count_bit, FRAME_BITS, "bit count");
|
||||
assert!(!decoded.crc_valid, "no key → crc_valid must be false");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_truncated_frame() {
|
||||
// A single repetition carrying only 65 of the 66 bits must NOT decode.
|
||||
let s = seed(0xDEADBEEF, 0x00ABCDEF, 0x0, 0x0, 0x02);
|
||||
let encoder = LandRoverRkeDecoder::new();
|
||||
let signal = encoder.encode(&s, 0x02).unwrap();
|
||||
// First repetition is preamble (20 pairs) + sync (1 pair) + 66 bit-pairs. Keep only the
|
||||
// first 65 bit-pairs so the frame is one bit short, and stop before the next repetition.
|
||||
let bits_to_keep = FRAME_BITS - 1;
|
||||
let partial_len = (PREAMBLE_COUNT as usize + 1 + bits_to_keep) * 2;
|
||||
let partial = &signal[..partial_len];
|
||||
assert!(decode_signal(partial).is_none(), "65-bit frame must not decode");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_wrong_sync_gap() {
|
||||
// Same PWM bits but a too-short "sync" gap must not be accepted as a frame.
|
||||
let s = seed(0x12345678, 0x00112233, 0x0, 0x0, 0x01);
|
||||
let encoder = LandRoverRkeDecoder::new();
|
||||
let mut signal = encoder.encode(&s, 0x01).unwrap();
|
||||
// Corrupt the first sync LOW (index = PREAMBLE_COUNT*2 + 1) to a Bit-0-like LOW (700µs),
|
||||
// which is far outside the 9600µs ±20% window.
|
||||
let sync_low_idx = PREAMBLE_COUNT as usize * 2 + 1;
|
||||
signal[sync_low_idx] = LevelDuration::new(false, 700);
|
||||
// Take just the first repetition so the later (intact) reps don't rescue the decode.
|
||||
let one_rep_len = (PREAMBLE_COUNT as usize + 1 + FRAME_BITS) * 2;
|
||||
let partial = &signal[..one_rep_len.min(signal.len())];
|
||||
assert!(decode_signal(partial).is_none(), "frame without valid 9.6ms sync must not decode");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,696 @@
|
||||
//! Land Rover V0 protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/land_rover_v0.c` and
|
||||
//! `land_rover_v0.h`. **Differential** Manchester (NOT the Flipper transition table): te_short 250,
|
||||
//! te_long 500, te_delta 100, with a ~750µs sync pulse and a ≥64-pair short preamble. FM.
|
||||
//!
|
||||
//! Frame: 81 bits = an 80-bit body (`raw[0..10]`) plus one trailing `extra_bit`. The 64-bit key
|
||||
//! reported as `DecodedSignal.data` is `raw[0..8]` big-endian; `raw[8..10]` is a 16-bit `tail`.
|
||||
//! Field layout in the key bytes:
|
||||
//! * bytes 0..3 → 24-bit command_signature (Lock = 0xC20363, Unlock = 0xA285E3)
|
||||
//! * bytes 3..6 → 24-bit serial
|
||||
//! * byte 6 + byte 7 MSB → 9-bit counter = `(b6 << 1) | (b7 >> 7)`
|
||||
//! * byte 7 bits 0x78 → 3 reserved bits (must be 0)
|
||||
//! * byte 7 bits 0x07 → 3-bit check
|
||||
//! The check is a proprietary 3-bit polynomial over the counter (`calculate_check`); the tail is
|
||||
//! 0xFFFF or 0x7FFF depending on a 1-bit parity of the counter (`calculate_tail`). Emission is gated
|
||||
//! on the reserved bits being zero, the check matching, the tail matching, and `extra_bit` set —
|
||||
//! so Land Rover V0 is strongly validated and will not false-match. Encoder supported.
|
||||
//!
|
||||
//! Decode steps: Reset → PreambleLow/PreambleHigh (count short pairs, ≥64) → SyncLow → Data.
|
||||
//! The Data step ports the C `process_transition`/`add_decoded_bit` differential machine directly:
|
||||
//! it tracks `previous_bit`, skips the initial boundary short-high pad (`boundary_pad_skipped`),
|
||||
//! and completes short half-bits via `pending_short`. Bit 0 (= 1) is seeded on entry to Data.
|
||||
//!
|
||||
//! Note on the C encoder (faithfully ported): `build_upload` forces frame bit 1 = 0 regardless of
|
||||
//! the key, so combined with the seeded bit 0 = 1 the top two frame bits are always `10`. The
|
||||
//! Unlock signature 0xA285E3 satisfies this and round-trips cleanly; the Lock signature 0xC20363
|
||||
//! (whose bit 1 is 1) is emitted as 0x820363 by the reference encoder. We reproduce that behaviour.
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 100;
|
||||
const SYNC_US: u32 = 750;
|
||||
const SYNC_DELTA_US: u32 = 120;
|
||||
const MIN_PREAMBLE_PAIRS: u16 = 64;
|
||||
const COUNT_BIT: usize = 81;
|
||||
const GAP_US: u32 = 50_000;
|
||||
|
||||
/// TX preamble pair count (matches LAND_ROVER_V0_PREAMBLE_PAIRS).
|
||||
const TX_PREAMBLE_PAIRS: usize = 319;
|
||||
|
||||
// Button signatures (LAND_ROVER_V0_SIG_*).
|
||||
const SIG_UNLOCK: u32 = 0x00A2_85E3;
|
||||
const SIG_LOCK: u32 = 0x00C2_0363;
|
||||
|
||||
// Land Rover button codes (LAND_ROVER_V0_BTN_*).
|
||||
const LR_BTN_UNKNOWN: u8 = 0x00;
|
||||
const LR_BTN_LOCK: u8 = 0x02;
|
||||
const LR_BTN_UNLOCK: u8 = 0x04;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
PreambleLow,
|
||||
PreambleHigh,
|
||||
SyncLow,
|
||||
Data,
|
||||
}
|
||||
|
||||
pub struct LandRoverV0Decoder {
|
||||
step: DecoderStep,
|
||||
preamble_count: u16,
|
||||
raw: [u8; 10],
|
||||
bit_count: u8,
|
||||
extra_bit: bool,
|
||||
previous_bit: bool,
|
||||
boundary_pad_skipped: bool,
|
||||
pending_short: bool,
|
||||
}
|
||||
|
||||
impl LandRoverV0Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
preamble_count: 0,
|
||||
raw: [0; 10],
|
||||
bit_count: 0,
|
||||
extra_bit: false,
|
||||
previous_bit: true,
|
||||
boundary_pad_skipped: false,
|
||||
pending_short: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_short(d: u32) -> bool {
|
||||
duration_diff!(d, TE_SHORT) < TE_DELTA
|
||||
}
|
||||
fn is_long(d: u32) -> bool {
|
||||
duration_diff!(d, TE_LONG) < TE_DELTA
|
||||
}
|
||||
fn is_sync(d: u32) -> bool {
|
||||
duration_diff!(d, SYNC_US) < SYNC_DELTA_US
|
||||
}
|
||||
|
||||
/// Reset the per-frame differential-Manchester state (matches the SyncLow init in the C feed).
|
||||
fn begin_frame(&mut self) {
|
||||
self.raw = [0; 10];
|
||||
self.bit_count = 0;
|
||||
self.extra_bit = false;
|
||||
self.previous_bit = true;
|
||||
self.boundary_pad_skipped = false;
|
||||
self.pending_short = false;
|
||||
}
|
||||
|
||||
/// Append one decoded bit (matches `land_rover_v0_add_decoded_bit`).
|
||||
/// Bits 0..80 pack MSB-first into `raw`; bit 80 is the trailing `extra_bit`.
|
||||
fn add_decoded_bit(&mut self, bit: bool) -> bool {
|
||||
if self.bit_count < 80 {
|
||||
let byte_index = (self.bit_count / 8) as usize;
|
||||
let bit_index = 7 - (self.bit_count % 8);
|
||||
if bit {
|
||||
self.raw[byte_index] |= 1u8 << bit_index;
|
||||
}
|
||||
} else if self.bit_count == 80 {
|
||||
self.extra_bit = bit;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
self.bit_count += 1;
|
||||
true
|
||||
}
|
||||
|
||||
/// Differential-Manchester transition handler (direct port of `land_rover_v0_process_transition`).
|
||||
fn process_transition(&mut self, level: bool, duration: u32) -> bool {
|
||||
if !self.boundary_pad_skipped {
|
||||
if level && Self::is_short(duration) {
|
||||
self.boundary_pad_skipped = true;
|
||||
return true;
|
||||
}
|
||||
self.boundary_pad_skipped = true;
|
||||
}
|
||||
|
||||
if self.pending_short {
|
||||
if !self.previous_bit && !level && Self::is_short(duration) {
|
||||
self.pending_short = false;
|
||||
return self.add_decoded_bit(false);
|
||||
} else if self.previous_bit && level && Self::is_short(duration) {
|
||||
self.pending_short = false;
|
||||
return self.add_decoded_bit(true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.previous_bit {
|
||||
if level && Self::is_long(duration) {
|
||||
self.previous_bit = true;
|
||||
return self.add_decoded_bit(true);
|
||||
} else if level && Self::is_short(duration) {
|
||||
self.pending_short = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if !level && Self::is_long(duration) {
|
||||
self.previous_bit = false;
|
||||
return self.add_decoded_bit(false);
|
||||
} else if !level && Self::is_short(duration) {
|
||||
self.pending_short = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// 3-bit check polynomial over the 9-bit counter (matches `land_rover_v0_calculate_check`).
|
||||
fn calculate_check(count: u32) -> u8 {
|
||||
let c0 = ((count >> 1) ^ (count >> 2) ^ (count >> 3) ^ (count >> 4) ^ (count >> 6)) & 1;
|
||||
let c1 = ((count >> 0)
|
||||
^ (count >> 2)
|
||||
^ (count >> 3)
|
||||
^ (count >> 4)
|
||||
^ (count >> 5)
|
||||
^ (count >> 6)
|
||||
^ 1)
|
||||
& 1;
|
||||
let c2 = ((count >> 1) ^ (count >> 3) ^ (count >> 4) ^ (count >> 5) ^ (count >> 6)) & 1;
|
||||
(c0 | (c1 << 1) | (c2 << 2)) as u8
|
||||
}
|
||||
|
||||
/// MSB selector for the 16-bit tail (matches `land_rover_v0_calculate_tail_msb`).
|
||||
fn calculate_tail_msb(count: u32) -> bool {
|
||||
(((count >> 0) ^ (count >> 2) ^ (count >> 4) ^ (count >> 5)) & 1) != 0
|
||||
}
|
||||
|
||||
/// 16-bit tail value (matches `land_rover_v0_calculate_tail`).
|
||||
fn calculate_tail(count: u32) -> u16 {
|
||||
if Self::calculate_tail_msb(count) {
|
||||
0xFFFF
|
||||
} else {
|
||||
0x7FFF
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a 24-bit command signature to a Land Rover button (matches
|
||||
/// `land_rover_v0_button_from_signature`).
|
||||
fn button_from_signature(signature: u32) -> u8 {
|
||||
match signature {
|
||||
SIG_UNLOCK => LR_BTN_UNLOCK,
|
||||
SIG_LOCK => LR_BTN_LOCK,
|
||||
_ => LR_BTN_UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
||||
/// Counter packed in key bytes 6 + 7-MSB (matches the C count extraction).
|
||||
fn count_from_bytes(b: &[u8; 8]) -> u32 {
|
||||
((b[6] as u32) << 1) | ((b[7] >> 7) & 1) as u32
|
||||
}
|
||||
|
||||
/// Validate a frame (matches `land_rover_v0_validate_frame`).
|
||||
/// Returns (check_ok, tail_ok).
|
||||
fn validate_frame(key: u64, tail: u16, extra_bit: bool) -> (bool, bool) {
|
||||
let b = key.to_be_bytes();
|
||||
let count = Self::count_from_bytes(&b);
|
||||
let expected_check = Self::calculate_check(count);
|
||||
let expected_tail = Self::calculate_tail(count);
|
||||
|
||||
let check_ok = (b[7] & 0x78) == 0 && (b[7] & 0x07) == expected_check;
|
||||
let tail_ok = tail == expected_tail && extra_bit;
|
||||
(check_ok, tail_ok)
|
||||
}
|
||||
|
||||
/// Finish a frame: validate, then build the decoded signal (matches
|
||||
/// `land_rover_v0_finish_frame` + `parse_key_fields`). Returns None when invalid (gated).
|
||||
fn finish_frame(&self) -> Option<DecodedSignal> {
|
||||
let key = u64::from_be_bytes([
|
||||
self.raw[0],
|
||||
self.raw[1],
|
||||
self.raw[2],
|
||||
self.raw[3],
|
||||
self.raw[4],
|
||||
self.raw[5],
|
||||
self.raw[6],
|
||||
self.raw[7],
|
||||
]);
|
||||
let tail = ((self.raw[8] as u16) << 8) | self.raw[9] as u16;
|
||||
|
||||
let (check_ok, tail_ok) = Self::validate_frame(key, tail, self.extra_bit);
|
||||
if !(check_ok && tail_ok) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let b = key.to_be_bytes();
|
||||
let signature = ((b[0] as u32) << 16) | ((b[1] as u32) << 8) | b[2] as u32;
|
||||
let serial = ((b[3] as u32) << 16) | ((b[4] as u32) << 8) | b[5] as u32;
|
||||
let count = Self::count_from_bytes(&b);
|
||||
let button = Self::button_from_signature(signature);
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(count as u16),
|
||||
crc_valid: true, // check + tail + reserved-bits validated above
|
||||
data: key,
|
||||
data_count_bit: COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
// Stash the 16-bit tail so the encoder can rebuild the full frame without recomputation.
|
||||
extra: Some(tail as u64),
|
||||
protocol_display_name: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Map KAT's generic button command to a Land Rover signature (Lock/Unlock).
|
||||
/// KAT: Lock=0x01, Unlock=0x02, Trunk=0x04, Panic=0x08. Land Rover V0 only defines Lock/Unlock,
|
||||
/// so Trunk/Panic have no signature and fall back to the decoded frame's signature in `encode`.
|
||||
fn signature_from_button(button: u8) -> u32 {
|
||||
match button {
|
||||
0x01 => SIG_LOCK, // KAT Lock
|
||||
0x02 => SIG_UNLOCK, // KAT Unlock
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the 64-bit key from fields (matches `land_rover_v0_build_key`).
|
||||
fn build_key(signature: u32, serial: u32, count: u32) -> u64 {
|
||||
let mut b = [0u8; 8];
|
||||
b[0] = (signature >> 16) as u8;
|
||||
b[1] = (signature >> 8) as u8;
|
||||
b[2] = signature as u8;
|
||||
b[3] = (serial >> 16) as u8;
|
||||
b[4] = (serial >> 8) as u8;
|
||||
b[5] = serial as u8;
|
||||
b[6] = (count >> 1) as u8;
|
||||
let counter_lsb = (count & 1) != 0;
|
||||
let check = Self::calculate_check(count);
|
||||
b[7] = (if counter_lsb { 0x80 } else { 0x00 }) | check;
|
||||
u64::from_be_bytes(b)
|
||||
}
|
||||
|
||||
fn enc_add_level(signal: &mut Vec<LevelDuration>, level: bool, duration: u32) {
|
||||
if let Some(last) = signal.last_mut() {
|
||||
if last.level == level {
|
||||
*last = LevelDuration::new(level, last.duration_us + duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
signal.push(LevelDuration::new(level, duration));
|
||||
}
|
||||
|
||||
/// Emit one differential-Manchester bit (matches `land_rover_v0_encoder_add_bit`).
|
||||
/// Returns the new `previous_bit`.
|
||||
fn enc_add_bit(signal: &mut Vec<LevelDuration>, previous_bit: bool, bit: bool) -> bool {
|
||||
match (previous_bit, bit) {
|
||||
(false, false) => {
|
||||
Self::enc_add_level(signal, true, TE_SHORT);
|
||||
Self::enc_add_level(signal, false, TE_SHORT);
|
||||
}
|
||||
(false, true) => {
|
||||
Self::enc_add_level(signal, true, TE_LONG);
|
||||
}
|
||||
(true, false) => {
|
||||
Self::enc_add_level(signal, false, TE_LONG);
|
||||
}
|
||||
(true, true) => {
|
||||
Self::enc_add_level(signal, false, TE_SHORT);
|
||||
Self::enc_add_level(signal, true, TE_SHORT);
|
||||
}
|
||||
}
|
||||
bit
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for LandRoverV0Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Land Rover V0"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: COUNT_BIT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[315_000_000, 433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.preamble_count = 0;
|
||||
self.raw = [0; 10];
|
||||
self.bit_count = 0;
|
||||
self.extra_bit = false;
|
||||
self.previous_bit = true;
|
||||
self.boundary_pad_skipped = false;
|
||||
self.pending_short = false;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if level && Self::is_short(duration) {
|
||||
self.preamble_count = 0;
|
||||
self.step = DecoderStep::PreambleLow;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::PreambleLow => {
|
||||
if !level && Self::is_short(duration) {
|
||||
self.preamble_count += 1;
|
||||
self.step = DecoderStep::PreambleHigh;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::PreambleHigh => {
|
||||
if level && Self::is_short(duration) {
|
||||
self.step = DecoderStep::PreambleLow;
|
||||
} else if level
|
||||
&& Self::is_sync(duration)
|
||||
&& self.preamble_count >= MIN_PREAMBLE_PAIRS
|
||||
{
|
||||
self.step = DecoderStep::SyncLow;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::SyncLow => {
|
||||
if !level && Self::is_sync(duration) {
|
||||
self.begin_frame();
|
||||
self.add_decoded_bit(true); // seed bit 0 = 1
|
||||
self.step = DecoderStep::Data;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Data => {
|
||||
if !self.process_transition(level, duration) {
|
||||
self.step = DecoderStep::Reset;
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.bit_count as usize == COUNT_BIT {
|
||||
let result = self.finish_frame();
|
||||
self.step = DecoderStep::Reset;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
// Derive fields from the decoded signal, preferring the requested button's signature.
|
||||
let serial = decoded.serial? & 0x00FF_FFFF;
|
||||
let count = (decoded.counter.unwrap_or(0) as u32) & 0x1FF;
|
||||
|
||||
// Pick the command signature: requested button first, else the decoded frame's signature.
|
||||
let mut signature = Self::signature_from_button(button);
|
||||
if signature == 0 {
|
||||
let b = decoded.data.to_be_bytes();
|
||||
signature = ((b[0] as u32) << 16) | ((b[1] as u32) << 8) | b[2] as u32;
|
||||
// If still not a known Land Rover signature, refuse (matches C: command_signature==0 → error).
|
||||
if Self::button_from_signature(signature) == LR_BTN_UNKNOWN {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let key = Self::build_key(signature, serial, count);
|
||||
let key_bytes = key.to_be_bytes();
|
||||
let tail = Self::calculate_tail(count);
|
||||
|
||||
// Capacity: preamble pairs (2 levels) + sync (3) + ~81 bits (≤2 levels each) + gap.
|
||||
let mut signal = Vec::with_capacity(TX_PREAMBLE_PAIRS * 2 + 3 + COUNT_BIT * 2 + 1);
|
||||
|
||||
// Preamble: alternating short high/low pairs.
|
||||
for _ in 0..TX_PREAMBLE_PAIRS {
|
||||
Self::enc_add_level(&mut signal, true, TE_SHORT);
|
||||
Self::enc_add_level(&mut signal, false, TE_SHORT);
|
||||
}
|
||||
|
||||
// Sync: high 750, low 750, then a boundary short-high pad (skipped by the decoder).
|
||||
Self::enc_add_level(&mut signal, true, SYNC_US);
|
||||
Self::enc_add_level(&mut signal, false, SYNC_US);
|
||||
Self::enc_add_level(&mut signal, true, TE_SHORT);
|
||||
|
||||
// Differential-Manchester body. previous_bit starts true (bit 0 = 1 is implied by the sync
|
||||
// trailing short). Bit index 1 is forced to 0 (matches the C build_upload), then bits 2..63
|
||||
// come from the key bytes.
|
||||
let mut previous_bit = true;
|
||||
previous_bit = Self::enc_add_bit(&mut signal, previous_bit, false);
|
||||
for bit_index in 2..64u8 {
|
||||
let byte_index = (bit_index / 8) as usize;
|
||||
let bit_in_byte = 7 - (bit_index % 8);
|
||||
let bit = (key_bytes[byte_index] >> bit_in_byte) & 1 != 0;
|
||||
previous_bit = Self::enc_add_bit(&mut signal, previous_bit, bit);
|
||||
}
|
||||
|
||||
// 16-bit tail, MSB first.
|
||||
for bit_index in 0..16u8 {
|
||||
let bit = (tail >> (15 - bit_index)) & 1 != 0;
|
||||
previous_bit = Self::enc_add_bit(&mut signal, previous_bit, bit);
|
||||
}
|
||||
|
||||
// Trailing extra bit = 1.
|
||||
let _ = Self::enc_add_bit(&mut signal, previous_bit, true);
|
||||
|
||||
// Inter-frame gap.
|
||||
Self::enc_add_level(&mut signal, false, GAP_US);
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LandRoverV0Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Drive a full encoded upload through the decoder and return the first decode.
|
||||
fn decode_upload(upload: &[LevelDuration]) -> Option<DecodedSignal> {
|
||||
let mut dec = LandRoverV0Decoder::new();
|
||||
for ld in upload {
|
||||
if let Some(sig) = dec.feed(ld.level, ld.duration_us) {
|
||||
return Some(sig);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// The 3-bit check polynomial matches the C reference for a couple of hand traces.
|
||||
#[test]
|
||||
fn check_polynomial_matches_reference() {
|
||||
// count = 0: c0=0, c1=(0^1)&1=1, c2=0 → 0b010 = 2
|
||||
assert_eq!(LandRoverV0Decoder::calculate_check(0), 0b010);
|
||||
// Spot-check internal consistency: build_key embeds calculate_check in byte 7.
|
||||
for count in 0..0x200u32 {
|
||||
let key = LandRoverV0Decoder::build_key(SIG_UNLOCK, 0x123456, count);
|
||||
let b = key.to_be_bytes();
|
||||
assert_eq!(
|
||||
b[7] & 0x07,
|
||||
LandRoverV0Decoder::calculate_check(count),
|
||||
"check embedded in key byte 7 must equal calculate_check(count) for count={count}"
|
||||
);
|
||||
assert_eq!(b[7] & 0x78, 0, "reserved bits must be zero for count={count}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The tail is 0xFFFF or 0x7FFF per the counter parity, matching the reference.
|
||||
#[test]
|
||||
fn tail_matches_reference() {
|
||||
for count in 0..0x200u32 {
|
||||
let tail = LandRoverV0Decoder::calculate_tail(count);
|
||||
assert!(tail == 0xFFFF || tail == 0x7FFF);
|
||||
let msb_set = (tail >> 15) & 1 == 1;
|
||||
assert_eq!(msb_set, LandRoverV0Decoder::calculate_tail_msb(count));
|
||||
}
|
||||
}
|
||||
|
||||
/// Primary correctness check: encode an Unlock frame and decode it back. The Unlock signature
|
||||
/// 0xA285E3 satisfies the encoder's forced-bit-1 = 0 invariant, so it round-trips cleanly:
|
||||
/// serial, button, counter, key and the 3-bit check all survive.
|
||||
#[test]
|
||||
fn unlock_round_trip_preserves_fields() {
|
||||
let serial = 0x00AB_CDEF & 0x00FF_FFFF;
|
||||
let counter = 0x123u16; // 9-bit counter
|
||||
let key = LandRoverV0Decoder::build_key(SIG_UNLOCK, serial, counter as u32);
|
||||
|
||||
let decoded_in = DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(LR_BTN_UNLOCK),
|
||||
counter: Some(counter),
|
||||
crc_valid: true,
|
||||
data: key,
|
||||
data_count_bit: COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: Some(LandRoverV0Decoder::calculate_tail(counter as u32) as u64),
|
||||
protocol_display_name: None,
|
||||
};
|
||||
|
||||
let dec = LandRoverV0Decoder::new();
|
||||
// KAT Unlock command = 0x02.
|
||||
let upload = dec.encode(&decoded_in, 0x02).expect("encode Unlock");
|
||||
let out = decode_upload(&upload).expect("decode the encoded Unlock frame");
|
||||
|
||||
assert!(out.crc_valid, "decoded frame must pass check+tail gating");
|
||||
assert_eq!(out.data, key, "64-bit key must survive the round trip");
|
||||
assert_eq!(out.serial, Some(serial), "serial must survive");
|
||||
assert_eq!(out.counter, Some(counter), "counter must survive");
|
||||
assert_eq!(out.button, Some(LR_BTN_UNLOCK), "Unlock button must survive");
|
||||
assert_eq!(out.data_count_bit, COUNT_BIT);
|
||||
// The stashed tail must match the counter-derived tail.
|
||||
assert_eq!(out.extra, Some(LandRoverV0Decoder::calculate_tail(counter as u32) as u64));
|
||||
// The embedded 3-bit check must match the recomputed value.
|
||||
let b = out.data.to_be_bytes();
|
||||
assert_eq!(b[7] & 0x07, LandRoverV0Decoder::calculate_check(counter as u32));
|
||||
assert_eq!(b[7] & 0x78, 0, "reserved bits zero");
|
||||
}
|
||||
|
||||
/// Round-trip across many serial/counter values for Unlock.
|
||||
#[test]
|
||||
fn unlock_round_trip_many() {
|
||||
let mut state: u32 = 0x1357_9BDF;
|
||||
let mut next = || {
|
||||
// xorshift for deterministic pseudo-random coverage
|
||||
state ^= state << 13;
|
||||
state ^= state >> 17;
|
||||
state ^= state << 5;
|
||||
state
|
||||
};
|
||||
|
||||
for _ in 0..256 {
|
||||
let serial = next() & 0x00FF_FFFF;
|
||||
let counter = (next() & 0x1FF) as u16;
|
||||
let key = LandRoverV0Decoder::build_key(SIG_UNLOCK, serial, counter as u32);
|
||||
let decoded_in = DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(LR_BTN_UNLOCK),
|
||||
counter: Some(counter),
|
||||
crc_valid: true,
|
||||
data: key,
|
||||
data_count_bit: COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: Some(LandRoverV0Decoder::calculate_tail(counter as u32) as u64),
|
||||
protocol_display_name: None,
|
||||
};
|
||||
let dec = LandRoverV0Decoder::new();
|
||||
let upload = dec.encode(&decoded_in, 0x02).expect("encode");
|
||||
let out = decode_upload(&upload)
|
||||
.unwrap_or_else(|| panic!("decode failed for serial={serial:06X} counter={counter:03X}"));
|
||||
assert_eq!(out.data, key);
|
||||
assert_eq!(out.serial, Some(serial));
|
||||
assert_eq!(out.counter, Some(counter));
|
||||
assert_eq!(out.button, Some(LR_BTN_UNLOCK));
|
||||
assert!(out.crc_valid);
|
||||
}
|
||||
}
|
||||
|
||||
/// Faithful-port note: the reference encoder forces frame bit 1 = 0. The Lock signature
|
||||
/// 0xC20363 has bit 1 = 1, so the emitted frame's signature becomes 0x820363, which maps to
|
||||
/// LR_BTN_UNKNOWN. We assert this exact reference behaviour rather than a "fixed" version.
|
||||
#[test]
|
||||
fn lock_signature_forced_bit_matches_reference_quirk() {
|
||||
let serial = 0x0012_3456;
|
||||
let counter = 100u16;
|
||||
let key = LandRoverV0Decoder::build_key(SIG_LOCK, serial, counter as u32);
|
||||
let decoded_in = DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(LR_BTN_LOCK),
|
||||
counter: Some(counter),
|
||||
crc_valid: true,
|
||||
data: key,
|
||||
data_count_bit: COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: Some(LandRoverV0Decoder::calculate_tail(counter as u32) as u64),
|
||||
protocol_display_name: None,
|
||||
};
|
||||
let dec = LandRoverV0Decoder::new();
|
||||
// KAT Lock command = 0x01.
|
||||
let upload = dec.encode(&decoded_in, 0x01).expect("encode Lock");
|
||||
let out = decode_upload(&upload).expect("frame still decodes (check/tail valid)");
|
||||
// Top byte 0xC2 -> 0x82 because bit 1 is forced to 0 by the encoder.
|
||||
let b = out.data.to_be_bytes();
|
||||
assert_eq!(b[0], 0x82, "encoder forces frame bit 1 = 0, turning 0xC2 into 0x82");
|
||||
assert_eq!(out.button, Some(LR_BTN_UNKNOWN), "0x820363 is not a known signature");
|
||||
// Serial / counter / check are still intact.
|
||||
assert_eq!(out.serial, Some(serial));
|
||||
assert_eq!(out.counter, Some(counter));
|
||||
assert!(out.crc_valid);
|
||||
}
|
||||
|
||||
/// A frame with a deliberately wrong check must NOT decode (gating prevents false matches).
|
||||
#[test]
|
||||
fn bad_check_is_rejected() {
|
||||
let serial = 0x00AB_CDEF;
|
||||
let counter = 0x055u16;
|
||||
let mut key = LandRoverV0Decoder::build_key(SIG_UNLOCK, serial, counter as u32);
|
||||
// Corrupt the 3-bit check in byte 7 (XOR a bit so it no longer matches).
|
||||
key ^= 0x01;
|
||||
let decoded_in = DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(LR_BTN_UNLOCK),
|
||||
counter: Some(counter),
|
||||
crc_valid: false,
|
||||
data: key,
|
||||
data_count_bit: COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
};
|
||||
// Hand-build an upload that transmits this corrupted key verbatim (no re-derivation):
|
||||
// reuse the encoder's level helpers but bypass build_key by injecting the raw key.
|
||||
let key_bytes = key.to_be_bytes();
|
||||
let tail = LandRoverV0Decoder::calculate_tail(counter as u32);
|
||||
let mut signal: Vec<LevelDuration> = Vec::new();
|
||||
for _ in 0..TX_PREAMBLE_PAIRS {
|
||||
LandRoverV0Decoder::enc_add_level(&mut signal, true, TE_SHORT);
|
||||
LandRoverV0Decoder::enc_add_level(&mut signal, false, TE_SHORT);
|
||||
}
|
||||
LandRoverV0Decoder::enc_add_level(&mut signal, true, SYNC_US);
|
||||
LandRoverV0Decoder::enc_add_level(&mut signal, false, SYNC_US);
|
||||
LandRoverV0Decoder::enc_add_level(&mut signal, true, TE_SHORT);
|
||||
let mut prev = true;
|
||||
prev = LandRoverV0Decoder::enc_add_bit(&mut signal, prev, false);
|
||||
for bit_index in 2..64u8 {
|
||||
let bi = (bit_index / 8) as usize;
|
||||
let bib = 7 - (bit_index % 8);
|
||||
let bit = (key_bytes[bi] >> bib) & 1 != 0;
|
||||
prev = LandRoverV0Decoder::enc_add_bit(&mut signal, prev, bit);
|
||||
}
|
||||
for bit_index in 0..16u8 {
|
||||
let bit = (tail >> (15 - bit_index)) & 1 != 0;
|
||||
prev = LandRoverV0Decoder::enc_add_bit(&mut signal, prev, bit);
|
||||
}
|
||||
let _ = LandRoverV0Decoder::enc_add_bit(&mut signal, prev, true);
|
||||
LandRoverV0Decoder::enc_add_level(&mut signal, false, GAP_US);
|
||||
|
||||
// The decoded count comes from the (corrupted) byte 7, so the embedded check now mismatches
|
||||
// calculate_check(count) → rejected. Note: corrupting bit 0 of the check does not change
|
||||
// the counter (counter uses byte 7 MSB only), so calculate_check(count) stays the same.
|
||||
let _ = decoded_in;
|
||||
assert!(
|
||||
decode_upload(&signal).is_none(),
|
||||
"frame with a corrupted check must be rejected by the gate"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,525 @@
|
||||
//! Mazda Siemens protocol decoder/encoder
|
||||
//!
|
||||
//! Ported from Flipper-ARF: `lib/subghz/protocols/mazda_siemens.c` / `mazda_siemens.h`
|
||||
//! (`SUBGHZ_PROTOCOL_MAZDA_SIEMENS_NAME = "MazdaSiemens"`). This is the Siemens/VDO keyfob
|
||||
//! cipher used on some Mazda vehicles — a DIFFERENT protocol from KAT's existing "Mazda V0"
|
||||
//! (Pandora) decoder, even though both ride a 250/500µs pair-based stream at 433.92 MHz FM.
|
||||
//!
|
||||
//! Profile (matches the C const block):
|
||||
//! - te_short = 250µs, te_long = 500µs, te_delta = 100µs, 64-bit frame.
|
||||
//! - Pair-based decoder: `feed()` ignores `level` and interprets raw durations in pairs
|
||||
//! (`process_pair`), collecting bits with *inverted* polarity (`state_bit == 0` → stored 1).
|
||||
//! - Preamble: ≥13 short/short pairs, then a short→long transition starts data; the first
|
||||
//! collected bit is a 1 (sync). A 14-byte buffer accumulates; on a non-matching pair the
|
||||
//! frame is checked: discard the leading sync byte, take 8 bytes, deobfuscate, validate.
|
||||
//! - Siemens obfuscation (the layer ported here, `mazda_xor_deobfuscate`):
|
||||
//! parity = byte_parity(data[7]); odd → mask = data[6], XOR bytes 0..6;
|
||||
//! even → mask = data[5], XOR bytes 0..5 and byte 6. Then bit-deinterleave bytes 5/6
|
||||
//! via `(old5 & 0xAA)|(old6 & 0x55)` / `(old5 & 0x55)|(old6 & 0xAA)`.
|
||||
//! The inner Siemens cipher's plaintext is left as-is (no key); only this obfuscation/
|
||||
//! interleave layer is reversed, matching the C.
|
||||
//! - Gate: additive checksum `sum(data[0..7]) == data[7]` (matches the C `mazda_check_completion`),
|
||||
//! plus the structural preamble/sync/bit-count constraints. This is what keeps it from
|
||||
//! false-matching other 250/500µs Manchester protocols.
|
||||
//! - Fields (`mazda_parse_data`): serial = data >> 32, button = (data >> 24) & 0xFF,
|
||||
//! counter = (data >> 8) & 0xFFFF. Button codes: 0x10 Lock, 0x20 Unlock, 0x40 Trunk.
|
||||
//! - RF: FM (`SubGhzProtocolFlag_FM`); 433.92 MHz only (`SubGhzProtocolFlag_433`).
|
||||
//!
|
||||
//! Encoder (`subghz_protocol_encoder_mazda_siemens_get_upload`): increments the counter byte,
|
||||
//! recomputes the checksum, bit-interleaves + XORs (`mazda_xor_obfuscate`), then emits a 12-byte
|
||||
//! 0xFF preamble, a 50ms gap, `0xFF 0xFF 0xD7`, the 8 obfuscated bytes transmitted as `255 - byte`,
|
||||
//! a `0x5A` tail byte, and a trailing 50ms gap. Manchester per byte: bit 1 → (H,L), bit 0 → (L,H),
|
||||
//! all at te_short. Implemented faithfully in `encode()`.
|
||||
//!
|
||||
//! Note: as in the C, the decoder and encoder are NOT a clean raw-timing round-trip pair (the
|
||||
//! decoder targets real fob air-format; the encoder generates a TX upload). The encode↔decode
|
||||
//! unit test therefore validates the obfuscate/deobfuscate (XOR + interleave) cipher layer,
|
||||
//! which is a true inverse.
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 100;
|
||||
const MIN_COUNT_BIT: usize = 64;
|
||||
|
||||
const PREAMBLE_MIN: u16 = 13;
|
||||
const COMPLETION_MIN: u16 = 80;
|
||||
const COMPLETION_MAX: u16 = 105;
|
||||
const DATA_BUFFER_SIZE: usize = 14;
|
||||
|
||||
// Encoder constants (mazda_siemens.c).
|
||||
const TX_PREAMBLE_BYTES: usize = 12;
|
||||
const TX_GAP_US: u32 = 50_000;
|
||||
const TX_SYNC_BYTE: u8 = 0xD7;
|
||||
const TX_TAIL_BYTE: u8 = 0x5A;
|
||||
|
||||
/// Button codes carried in the frame (mazda_get_btn_name in the C).
|
||||
const BTN_LOCK: u8 = 0x10;
|
||||
const BTN_UNLOCK: u8 = 0x20;
|
||||
const BTN_TRUNK: u8 = 0x40;
|
||||
|
||||
/// Decoder states (matches mazda_siemens.c MazdaSiemensDecoderStep).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
PreambleSave,
|
||||
PreambleCheck,
|
||||
DataSave,
|
||||
DataCheck,
|
||||
}
|
||||
|
||||
/// Mazda Siemens protocol decoder.
|
||||
pub struct MazdaSiemensDecoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
preamble_count: u16,
|
||||
bit_counter: u16,
|
||||
prev_state: u8,
|
||||
data_buffer: [u8; DATA_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl MazdaSiemensDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
preamble_count: 0,
|
||||
bit_counter: 0,
|
||||
prev_state: 0,
|
||||
data_buffer: [0u8; DATA_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_short(duration: u32) -> bool {
|
||||
duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_long(duration: u32) -> bool {
|
||||
duration_diff!(duration, TE_LONG) < TE_DELTA
|
||||
}
|
||||
|
||||
/// Collect one bit into the buffer with inverted polarity (mazda_collect_bit).
|
||||
/// `state_bit == 0` stores a 1.
|
||||
fn collect_bit(&mut self, state_bit: u8) {
|
||||
let byte_idx = (self.bit_counter >> 3) as usize;
|
||||
if byte_idx < DATA_BUFFER_SIZE {
|
||||
self.data_buffer[byte_idx] <<= 1;
|
||||
if state_bit == 0 {
|
||||
self.data_buffer[byte_idx] |= 1;
|
||||
}
|
||||
}
|
||||
self.bit_counter += 1;
|
||||
}
|
||||
|
||||
/// Process a duration pair (mazda_process_pair). Returns true if the pair was valid.
|
||||
fn process_pair(&mut self, dur_first: u32, dur_second: u32) -> bool {
|
||||
let first_short = Self::is_short(dur_first);
|
||||
let first_long = Self::is_long(dur_first);
|
||||
let second_short = Self::is_short(dur_second);
|
||||
let second_long = Self::is_long(dur_second);
|
||||
|
||||
if first_long && second_short {
|
||||
self.collect_bit(0);
|
||||
self.collect_bit(1);
|
||||
self.prev_state = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if first_short && second_long {
|
||||
self.collect_bit(1);
|
||||
self.prev_state = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if first_short && second_short {
|
||||
let ps = self.prev_state;
|
||||
self.collect_bit(ps);
|
||||
return true;
|
||||
}
|
||||
|
||||
if first_long && second_long {
|
||||
self.collect_bit(0);
|
||||
self.collect_bit(1);
|
||||
self.prev_state = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Validate a complete frame (mazda_check_completion). On success returns a DecodedSignal.
|
||||
fn check_completion(&self) -> Option<DecodedSignal> {
|
||||
if self.bit_counter < COMPLETION_MIN || self.bit_counter > COMPLETION_MAX {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Shift buffer by 1 byte (discard the sync/header byte).
|
||||
let mut data = [0u8; 8];
|
||||
for i in 0..8 {
|
||||
data[i] = self.data_buffer[i + 1];
|
||||
}
|
||||
|
||||
Self::xor_deobfuscate(&mut data);
|
||||
|
||||
// Additive checksum: sum(data[0..7]) must equal data[7].
|
||||
let mut checksum: u8 = 0;
|
||||
for &b in data.iter().take(7) {
|
||||
checksum = checksum.wrapping_add(b);
|
||||
}
|
||||
if checksum != data[7] {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Pack into u64 (big-endian byte order).
|
||||
let packed = u64::from_be_bytes(data);
|
||||
|
||||
let (serial, button, counter) = Self::parse_fields(packed);
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid: true,
|
||||
data: packed,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Field extraction (mazda_parse_data).
|
||||
fn parse_fields(packed: u64) -> (u32, u8, u16) {
|
||||
let serial = (packed >> 32) as u32;
|
||||
let button = ((packed >> 24) & 0xFF) as u8;
|
||||
let counter = ((packed >> 8) & 0xFFFF) as u16;
|
||||
(serial, button, counter)
|
||||
}
|
||||
|
||||
/// Byte parity: XOR-fold to a single bit (mazda_byte_parity).
|
||||
fn byte_parity(mut val: u8) -> u8 {
|
||||
val ^= val >> 4;
|
||||
val ^= val >> 2;
|
||||
val ^= val >> 1;
|
||||
val & 1
|
||||
}
|
||||
|
||||
/// Siemens RX deobfuscation (mazda_xor_deobfuscate):
|
||||
/// parity-dependent XOR mask, then deinterleave bytes 5/6.
|
||||
fn xor_deobfuscate(data: &mut [u8; 8]) {
|
||||
let parity = Self::byte_parity(data[7]);
|
||||
|
||||
if parity != 0 {
|
||||
// Odd parity: mask = byte[6], XOR bytes 0..6.
|
||||
let mask = data[6];
|
||||
for i in 0..6 {
|
||||
data[i] ^= mask;
|
||||
}
|
||||
} else {
|
||||
// Even parity: mask = byte[5], XOR bytes 0..5 and byte[6].
|
||||
let mask = data[5];
|
||||
for i in 0..5 {
|
||||
data[i] ^= mask;
|
||||
}
|
||||
data[6] ^= mask;
|
||||
}
|
||||
|
||||
// Bit deinterleave bytes 5/6.
|
||||
let old5 = data[5];
|
||||
let old6 = data[6];
|
||||
data[5] = (old5 & 0xAA) | (old6 & 0x55);
|
||||
data[6] = (old5 & 0x55) | (old6 & 0xAA);
|
||||
}
|
||||
|
||||
/// Siemens TX obfuscation (mazda_xor_obfuscate): interleave bytes 5/6, then
|
||||
/// parity-dependent XOR mask. Inverse of `xor_deobfuscate`.
|
||||
fn xor_obfuscate(data: &mut [u8; 8]) {
|
||||
let old5 = data[5];
|
||||
let old6 = data[6];
|
||||
data[5] = (old5 & 0xAA) | (old6 & 0x55);
|
||||
data[6] = (old5 & 0x55) | (old6 & 0xAA);
|
||||
|
||||
let parity = Self::byte_parity(data[7]);
|
||||
|
||||
if parity != 0 {
|
||||
let mask = data[6];
|
||||
for i in 0..6 {
|
||||
data[i] ^= mask;
|
||||
}
|
||||
} else {
|
||||
let mask = data[5];
|
||||
for i in 0..5 {
|
||||
data[i] ^= mask;
|
||||
}
|
||||
data[6] ^= mask;
|
||||
}
|
||||
}
|
||||
|
||||
/// Button name for display (mazda_get_btn_name).
|
||||
#[allow(dead_code)]
|
||||
fn get_button_name(btn: u8) -> &'static str {
|
||||
match btn {
|
||||
BTN_LOCK => "Lock",
|
||||
BTN_UNLOCK => "Unlock",
|
||||
BTN_TRUNK => "Trunk",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
/// Map KAT's generic button command to a Mazda Siemens frame button code.
|
||||
fn map_button(button: u8) -> u8 {
|
||||
match button {
|
||||
0x01 => BTN_LOCK, // Lock
|
||||
0x02 => BTN_UNLOCK, // Unlock
|
||||
0x04 => BTN_TRUNK, // Trunk
|
||||
BTN_LOCK | BTN_UNLOCK | BTN_TRUNK => button, // already a frame code
|
||||
_ => BTN_UNLOCK,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode one byte as Manchester, MSB-first: bit 1 → (H,L), bit 0 → (L,H) (mazda_encode_byte).
|
||||
fn enc_byte(signal: &mut Vec<LevelDuration>, byte: u8) {
|
||||
for bit in (0..8).rev() {
|
||||
if (byte >> bit) & 1 != 0 {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for MazdaSiemensDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Mazda Siemens"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: MIN_COUNT_BIT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
// SubGhzProtocolFlag_433 only.
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.te_last = 0;
|
||||
self.preamble_count = 0;
|
||||
self.bit_counter = 0;
|
||||
self.prev_state = 0;
|
||||
self.data_buffer = [0u8; DATA_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
fn feed(&mut self, _level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if Self::is_short(duration) {
|
||||
self.te_last = duration;
|
||||
self.preamble_count = 0;
|
||||
self.step = DecoderStep::PreambleCheck;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::PreambleSave => {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::PreambleCheck;
|
||||
}
|
||||
|
||||
DecoderStep::PreambleCheck => {
|
||||
if Self::is_short(self.te_last) && Self::is_short(duration) {
|
||||
self.preamble_count += 1;
|
||||
self.step = DecoderStep::PreambleSave;
|
||||
} else if Self::is_short(self.te_last)
|
||||
&& Self::is_long(duration)
|
||||
&& self.preamble_count >= PREAMBLE_MIN
|
||||
{
|
||||
// Preamble → data: seed the leading sync bit (a 1).
|
||||
self.bit_counter = 1;
|
||||
self.data_buffer = [0u8; DATA_BUFFER_SIZE];
|
||||
self.collect_bit(1);
|
||||
self.prev_state = 0;
|
||||
self.step = DecoderStep::DataSave;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::DataSave => {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::DataCheck;
|
||||
}
|
||||
|
||||
DecoderStep::DataCheck => {
|
||||
if self.process_pair(self.te_last, duration) {
|
||||
self.step = DecoderStep::DataSave;
|
||||
} else {
|
||||
let result = self.check_completion();
|
||||
self.step = DecoderStep::Reset;
|
||||
if result.is_some() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
// Rebuild the 8 cleartext bytes from the decoded 64-bit word.
|
||||
let mut data = decoded.data.to_be_bytes();
|
||||
|
||||
// Apply the requested button into the frame's button field (byte index 3 = data >> 24).
|
||||
data[3] = Self::map_button(button);
|
||||
|
||||
// Increment the counter byte (mazda_siemens.c get_upload): data[6]++ with carry into data[5].
|
||||
let (new6, carry) = data[6].overflowing_add(1);
|
||||
data[6] = new6;
|
||||
if carry {
|
||||
data[5] = data[5].wrapping_add(1);
|
||||
}
|
||||
|
||||
// Recompute the additive checksum over bytes 0..7.
|
||||
let mut checksum: u8 = 0;
|
||||
for &b in data.iter().take(7) {
|
||||
checksum = checksum.wrapping_add(b);
|
||||
}
|
||||
data[7] = checksum;
|
||||
|
||||
// Obfuscate (interleave + XOR) for transmission.
|
||||
let mut tx_data = data;
|
||||
Self::xor_obfuscate(&mut tx_data);
|
||||
|
||||
// Build the upload: 12x 0xFF preamble, gap, 0xFF 0xFF, sync 0xD7,
|
||||
// 8 data bytes transmitted inverted (255 - byte), tail 0x5A, trailing gap.
|
||||
let mut signal: Vec<LevelDuration> = Vec::with_capacity((TX_PREAMBLE_BYTES + 12) * 16 + 4);
|
||||
for _ in 0..TX_PREAMBLE_BYTES {
|
||||
Self::enc_byte(&mut signal, 0xFF);
|
||||
}
|
||||
signal.push(LevelDuration::new(false, TX_GAP_US));
|
||||
Self::enc_byte(&mut signal, 0xFF);
|
||||
Self::enc_byte(&mut signal, 0xFF);
|
||||
Self::enc_byte(&mut signal, TX_SYNC_BYTE);
|
||||
for &b in tx_data.iter() {
|
||||
Self::enc_byte(&mut signal, 255 - b);
|
||||
}
|
||||
Self::enc_byte(&mut signal, TX_TAIL_BYTE);
|
||||
signal.push(LevelDuration::new(false, TX_GAP_US));
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MazdaSiemensDecoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The obfuscate/deobfuscate (XOR + bit-interleave) layer is a true inverse for every
|
||||
/// valid cleartext (checksum byte fixes the parity branch). This is the cipher-layer
|
||||
/// round-trip the decoder relies on; validates the ported Siemens obfuscation.
|
||||
#[test]
|
||||
fn xor_layer_round_trips() {
|
||||
// Deterministic LCG over many cleartexts: 7 data bytes + matching additive checksum.
|
||||
let mut seed: u32 = 0x1234_5678;
|
||||
let mut next = || {
|
||||
seed = seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
|
||||
(seed >> 16) as u8
|
||||
};
|
||||
for _ in 0..20_000 {
|
||||
let mut clear = [0u8; 8];
|
||||
let mut sum: u8 = 0;
|
||||
for b in clear.iter_mut().take(7) {
|
||||
*b = next();
|
||||
sum = sum.wrapping_add(*b);
|
||||
}
|
||||
clear[7] = sum;
|
||||
|
||||
let mut buf = clear;
|
||||
MazdaSiemensDecoder::xor_obfuscate(&mut buf);
|
||||
MazdaSiemensDecoder::xor_deobfuscate(&mut buf);
|
||||
assert_eq!(buf, clear, "obfuscate→deobfuscate must be identity");
|
||||
}
|
||||
}
|
||||
|
||||
/// Deobfuscate is the exact inverse of obfuscate for both parity branches (odd: byte7
|
||||
/// has odd popcount; even: zero). Pins the parity-dependent mask selection.
|
||||
#[test]
|
||||
fn xor_both_parity_branches() {
|
||||
// Even-parity checksum byte (0x00 → parity 0): mask = byte[5].
|
||||
let mut even = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x00];
|
||||
let orig_even = even;
|
||||
MazdaSiemensDecoder::xor_obfuscate(&mut even);
|
||||
MazdaSiemensDecoder::xor_deobfuscate(&mut even);
|
||||
assert_eq!(even, orig_even);
|
||||
assert_eq!(MazdaSiemensDecoder::byte_parity(0x00), 0);
|
||||
|
||||
// Odd-parity checksum byte (0x01 → parity 1): mask = byte[6].
|
||||
let mut odd = [0xAAu8, 0xBB, 0xCC, 0xDD, 0xEE, 0x12, 0x34, 0x01];
|
||||
let orig_odd = odd;
|
||||
MazdaSiemensDecoder::xor_obfuscate(&mut odd);
|
||||
MazdaSiemensDecoder::xor_deobfuscate(&mut odd);
|
||||
assert_eq!(odd, orig_odd);
|
||||
assert_eq!(MazdaSiemensDecoder::byte_parity(0x01), 1);
|
||||
}
|
||||
|
||||
/// Field extraction matches mazda_parse_data: serial = >>32, button = >>24, counter = >>8.
|
||||
#[test]
|
||||
fn parse_fields_layout() {
|
||||
let packed: u64 = 0x1234_5678_2000_06_3A;
|
||||
let (serial, button, counter) = MazdaSiemensDecoder::parse_fields(packed);
|
||||
assert_eq!(serial, 0x1234_5678);
|
||||
assert_eq!(button, 0x20);
|
||||
assert_eq!(counter, 0x0006);
|
||||
}
|
||||
|
||||
/// The encoder produces a non-empty Manchester upload with the expected leading 0xFF
|
||||
/// preamble (all alternating short pulses) and a 50ms gap. Smoke test of the TX path.
|
||||
#[test]
|
||||
fn encode_produces_upload() {
|
||||
let dec = MazdaSiemensDecoder::new();
|
||||
let signal = DecodedSignal {
|
||||
serial: Some(0x1234_5678),
|
||||
button: Some(0x02),
|
||||
counter: Some(0x0005),
|
||||
crc_valid: true,
|
||||
data: 0x1234_5678_2000_05_00,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
};
|
||||
let up = dec.encode(&signal, 0x02).expect("encode should succeed");
|
||||
assert!(!up.is_empty());
|
||||
// First 16 pulses are the first 0xFF preamble byte: alternating H/L shorts.
|
||||
for (i, p) in up.iter().take(16).enumerate() {
|
||||
assert_eq!(p.duration_us, TE_SHORT);
|
||||
assert_eq!(p.level, i % 2 == 0, "0xFF Manchester alternates H,L starting high");
|
||||
}
|
||||
// A 50ms gap is present somewhere in the upload.
|
||||
assert!(up.iter().any(|p| p.duration_us == TX_GAP_US));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
//! Mazda V0 protocol decoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/mazda_v0.c`.
|
||||
//! Original implementation by @lupettohf.
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - 433.92 MHz FM, decode-only (no encoder)
|
||||
//! - Pair-based decoding: feed() ignores level, processes raw durations in pairs
|
||||
//! - Preamble: minimum 13 short/short pairs before data starts
|
||||
//! - Data uses a 14-byte buffer with inverted bit polarity
|
||||
//! - XOR deobfuscation with parity-based mask selection + bit interleave swap
|
||||
//! - Additive checksum over first 7 bytes must equal byte 8
|
||||
//! - Field layout: serial (32-bit), button (8-bit), counter (16-bit)
|
||||
//! - Button codes: 0x10=Lock, 0x20=Unlock, 0x40=Trunk
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 100;
|
||||
const MIN_COUNT_BIT: usize = 64;
|
||||
|
||||
const PREAMBLE_MIN: u16 = 13;
|
||||
const COMPLETION_MIN: u16 = 80;
|
||||
const COMPLETION_MAX: u16 = 105;
|
||||
const DATA_BUFFER_SIZE: usize = 14;
|
||||
|
||||
/// Decoder states (matches mazda_v0.c MazdaDecoderStep)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
PreambleSave,
|
||||
PreambleCheck,
|
||||
DataSave,
|
||||
DataCheck,
|
||||
}
|
||||
|
||||
/// Mazda V0 protocol decoder
|
||||
pub struct MazdaV0Decoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
preamble_count: u16,
|
||||
bit_counter: u16,
|
||||
prev_state: u8,
|
||||
data_buffer: [u8; DATA_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl MazdaV0Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
preamble_count: 0,
|
||||
bit_counter: 0,
|
||||
prev_state: 0,
|
||||
data_buffer: [0u8; DATA_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_short(duration: u32) -> bool {
|
||||
duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_long(duration: u32) -> bool {
|
||||
duration_diff!(duration, TE_LONG) < TE_DELTA
|
||||
}
|
||||
|
||||
/// Collect a single bit into the data buffer.
|
||||
/// Inverted polarity: state_bit == 0 means stored bit is 1.
|
||||
fn collect_bit(&mut self, state_bit: u8) {
|
||||
let byte_idx = (self.bit_counter >> 3) as usize;
|
||||
if byte_idx < DATA_BUFFER_SIZE {
|
||||
self.data_buffer[byte_idx] <<= 1;
|
||||
if state_bit == 0 {
|
||||
self.data_buffer[byte_idx] |= 1;
|
||||
}
|
||||
}
|
||||
self.bit_counter += 1;
|
||||
}
|
||||
|
||||
/// Process a duration pair and collect bits.
|
||||
/// Returns true if the pair was valid, false otherwise.
|
||||
fn process_pair(&mut self, dur_first: u32, dur_second: u32) -> bool {
|
||||
let first_short = Self::is_short(dur_first);
|
||||
let first_long = Self::is_long(dur_first);
|
||||
let second_short = Self::is_short(dur_second);
|
||||
let second_long = Self::is_long(dur_second);
|
||||
|
||||
if first_long && second_short {
|
||||
self.collect_bit(0);
|
||||
self.collect_bit(1);
|
||||
self.prev_state = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if first_short && second_long {
|
||||
self.collect_bit(1);
|
||||
self.prev_state = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if first_short && second_short {
|
||||
let ps = self.prev_state;
|
||||
self.collect_bit(ps);
|
||||
return true;
|
||||
}
|
||||
|
||||
if first_long && second_long {
|
||||
self.collect_bit(0);
|
||||
self.collect_bit(1);
|
||||
self.prev_state = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check whether enough bits have been collected and validate the frame.
|
||||
/// On success, returns a DecodedSignal.
|
||||
fn check_completion(&self) -> Option<DecodedSignal> {
|
||||
if self.bit_counter < COMPLETION_MIN || self.bit_counter > COMPLETION_MAX {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Shift buffer by 1 byte (discard sync/header byte)
|
||||
let mut data = [0u8; 8];
|
||||
for i in 0..8 {
|
||||
data[i] = self.data_buffer[i + 1];
|
||||
}
|
||||
|
||||
// XOR deobfuscation
|
||||
Self::xor_deobfuscate(&mut data);
|
||||
|
||||
// Checksum: sum of data[0..7] must equal data[7]
|
||||
let mut checksum: u8 = 0;
|
||||
for i in 0..7 {
|
||||
checksum = checksum.wrapping_add(data[i]);
|
||||
}
|
||||
if checksum != data[7] {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Pack into u64
|
||||
let mut packed: u64 = 0;
|
||||
for i in 0..8 {
|
||||
packed = (packed << 8) | (data[i] as u64);
|
||||
}
|
||||
|
||||
// Parse fields
|
||||
let serial = (packed >> 32) as u32;
|
||||
let btn = ((packed >> 24) & 0xFF) as u8;
|
||||
let cnt = ((packed >> 8) & 0xFFFF) as u16;
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(btn),
|
||||
counter: Some(cnt),
|
||||
crc_valid: true,
|
||||
data: packed,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Byte parity: XOR-fold to single bit (matches mazda_byte_parity in C)
|
||||
fn byte_parity(mut value: u8) -> u8 {
|
||||
value ^= value >> 4;
|
||||
value ^= value >> 2;
|
||||
value ^= value >> 1;
|
||||
value & 1
|
||||
}
|
||||
|
||||
/// XOR deobfuscation with parity-based mask selection + bit interleave swap
|
||||
/// (matches mazda_xor_deobfuscate in C)
|
||||
fn xor_deobfuscate(data: &mut [u8; 8]) {
|
||||
let parity = Self::byte_parity(data[7]);
|
||||
|
||||
if parity != 0 {
|
||||
let mask = data[6];
|
||||
for i in 0..6 {
|
||||
data[i] ^= mask;
|
||||
}
|
||||
} else {
|
||||
let mask = data[5];
|
||||
for i in 0..5 {
|
||||
data[i] ^= mask;
|
||||
}
|
||||
data[6] ^= mask;
|
||||
}
|
||||
|
||||
// Bit interleave swap between bytes 5 and 6
|
||||
let old5 = data[5];
|
||||
let old6 = data[6];
|
||||
data[5] = (old5 & 0xAA) | (old6 & 0x55);
|
||||
data[6] = (old5 & 0x55) | (old6 & 0xAA);
|
||||
}
|
||||
|
||||
/// Get button name for display
|
||||
#[allow(dead_code)]
|
||||
fn get_button_name(btn: u8) -> &'static str {
|
||||
match btn {
|
||||
0x10 => "Lock",
|
||||
0x20 => "Unlock",
|
||||
0x40 => "Trunk",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for MazdaV0Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Mazda V0"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: MIN_COUNT_BIT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.preamble_count = 0;
|
||||
self.bit_counter = 0;
|
||||
self.prev_state = 0;
|
||||
self.te_last = 0;
|
||||
self.data_buffer = [0u8; DATA_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
fn feed(&mut self, _level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if Self::is_short(duration) {
|
||||
self.te_last = duration;
|
||||
self.preamble_count = 0;
|
||||
self.step = DecoderStep::PreambleCheck;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::PreambleSave => {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::PreambleCheck;
|
||||
}
|
||||
|
||||
DecoderStep::PreambleCheck => {
|
||||
if Self::is_short(self.te_last) && Self::is_short(duration) {
|
||||
self.preamble_count += 1;
|
||||
self.step = DecoderStep::PreambleSave;
|
||||
} else if Self::is_short(self.te_last)
|
||||
&& Self::is_long(duration)
|
||||
&& self.preamble_count >= PREAMBLE_MIN
|
||||
{
|
||||
// Transition from preamble to data
|
||||
self.bit_counter = 1;
|
||||
self.data_buffer = [0u8; DATA_BUFFER_SIZE];
|
||||
self.collect_bit(1);
|
||||
self.prev_state = 0;
|
||||
self.step = DecoderStep::DataSave;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::DataSave => {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::DataCheck;
|
||||
}
|
||||
|
||||
DecoderStep::DataCheck => {
|
||||
if self.process_pair(self.te_last, duration) {
|
||||
self.step = DecoderStep::DataSave;
|
||||
} else {
|
||||
// Pair was invalid - check if we have a complete frame
|
||||
let result = self.check_completion();
|
||||
self.step = DecoderStep::Reset;
|
||||
if result.is_some() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MazdaV0Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
//! Mitsubishi V0 protocol decoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/mitsubishi_v0.c`.
|
||||
//! Original implementation by @lupettohf.
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - PWM encoding: Short HIGH + Long LOW = bit 1, Long HIGH + Short LOW = bit 0
|
||||
//! - 96-bit frame (12 bytes), collected MSB-first into byte buffer
|
||||
//! - Level-aware state machine: HIGH pulses are saved, LOW pulses complete the pair
|
||||
//! - Unscramble: NOT first 8 bytes, extract counter from bytes[4..5], compute XOR mask, apply to bytes[0..5]
|
||||
//! - Field layout: serial = bytes[0..3] (32-bit), counter = bytes[4..5] (16-bit), button = byte[6]
|
||||
//! - 868 MHz, FM modulation, decode-only (no encoder)
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 100;
|
||||
#[allow(dead_code)]
|
||||
const MIN_COUNT_BIT: usize = 80;
|
||||
const BIT_COUNT: usize = 96;
|
||||
const DATA_BYTES: usize = 12;
|
||||
|
||||
/// Decoder states (matches mitsubishi_v0.c MitsubishiDecoderStep)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
DataSave,
|
||||
DataCheck,
|
||||
}
|
||||
|
||||
/// Mitsubishi V0 protocol decoder
|
||||
pub struct MitsubishiV0Decoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
bit_count: usize,
|
||||
decode_data: [u8; DATA_BYTES],
|
||||
}
|
||||
|
||||
impl MitsubishiV0Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
bit_count: 0,
|
||||
decode_data: [0u8; DATA_BYTES],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_short(duration: u32) -> bool {
|
||||
duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_long(duration: u32) -> bool {
|
||||
duration_diff!(duration, TE_LONG) < TE_DELTA
|
||||
}
|
||||
|
||||
fn reset_payload(&mut self) {
|
||||
self.bit_count = 0;
|
||||
self.decode_data = [0u8; DATA_BYTES];
|
||||
}
|
||||
|
||||
/// Collect a HIGH+LOW pair and decode one bit.
|
||||
/// Short HIGH + Long LOW = bit 1; Long HIGH + Short LOW = bit 0.
|
||||
/// Bits are stored MSB-first into the byte buffer.
|
||||
fn collect_pair(&mut self, high: u32, low: u32) -> bool {
|
||||
let bit_value;
|
||||
|
||||
if Self::is_short(high) && Self::is_long(low) {
|
||||
bit_value = true;
|
||||
} else if Self::is_long(high) && Self::is_short(low) {
|
||||
bit_value = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
let bit_index = self.bit_count;
|
||||
if bit_index < BIT_COUNT {
|
||||
if bit_value {
|
||||
let byte_index = bit_index >> 3;
|
||||
let bit_position = 7 - (bit_index & 0x07);
|
||||
self.decode_data[byte_index] |= 1u8 << bit_position;
|
||||
}
|
||||
self.bit_count += 1;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Unscramble the payload (matches mitsubishi_unscramble_payload in C reference).
|
||||
/// 1. Bitwise NOT first 8 bytes
|
||||
/// 2. Extract counter from bytes[4..5]
|
||||
/// 3. Compute masks from counter and XOR bytes[0..5]
|
||||
fn unscramble_payload(payload: &mut [u8; DATA_BYTES]) {
|
||||
// Step 1: NOT first 8 bytes
|
||||
for i in 0..8 {
|
||||
payload[i] = !payload[i];
|
||||
}
|
||||
|
||||
// Step 2: Extract counter
|
||||
let counter = ((payload[4] as u16) << 8) | (payload[5] as u16);
|
||||
let hi = ((counter >> 8) & 0xFF) as u8;
|
||||
let lo = (counter & 0xFF) as u8;
|
||||
|
||||
// Step 3: Compute masks
|
||||
let mask1 = (hi & 0xAA) | (lo & 0x55);
|
||||
let mask2 = (lo & 0xAA) | (hi & 0x55);
|
||||
let mask3 = mask1 ^ mask2;
|
||||
|
||||
// Step 4: XOR bytes[0..5] with mask3
|
||||
for i in 0..5 {
|
||||
payload[i] ^= mask3;
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the unscrambled payload into a DecodedSignal.
|
||||
fn publish_frame(&self) -> DecodedSignal {
|
||||
let mut payload = self.decode_data;
|
||||
Self::unscramble_payload(&mut payload);
|
||||
|
||||
let serial = ((payload[0] as u32) << 24)
|
||||
| ((payload[1] as u32) << 16)
|
||||
| ((payload[2] as u32) << 8)
|
||||
| (payload[3] as u32);
|
||||
|
||||
let counter = ((payload[4] as u16) << 8) | (payload[5] as u16);
|
||||
let button = payload[6];
|
||||
|
||||
// Store first 8 bytes (post-unscramble) as u64 data field
|
||||
let data = ((payload[0] as u64) << 56)
|
||||
| ((payload[1] as u64) << 48)
|
||||
| ((payload[2] as u64) << 40)
|
||||
| ((payload[3] as u64) << 32)
|
||||
| ((payload[4] as u64) << 24)
|
||||
| ((payload[5] as u64) << 16)
|
||||
| ((payload[6] as u64) << 8)
|
||||
| (payload[7] as u64);
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid: true,
|
||||
data,
|
||||
data_count_bit: BIT_COUNT,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for MitsubishiV0Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Mitsubishi V0"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: MIN_COUNT_BIT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[868_350_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.te_last = 0;
|
||||
self.reset_payload();
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
// Wait for a HIGH pulse to start
|
||||
if level {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::DataCheck;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::DataSave => {
|
||||
if level {
|
||||
// HIGH pulse: save duration and move to DataCheck
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::DataCheck;
|
||||
} else {
|
||||
// LOW pulse without preceding HIGH data check: reset
|
||||
self.step = DecoderStep::Reset;
|
||||
self.reset_payload();
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::DataCheck => {
|
||||
if !level {
|
||||
// LOW pulse: complete the HIGH+LOW pair
|
||||
if self.collect_pair(self.te_last, duration) {
|
||||
if self.bit_count >= BIT_COUNT {
|
||||
// Full frame received
|
||||
let result = self.publish_frame();
|
||||
self.reset_payload();
|
||||
self.step = DecoderStep::Reset;
|
||||
return Some(result);
|
||||
} else {
|
||||
self.step = DecoderStep::DataSave;
|
||||
}
|
||||
} else {
|
||||
// Invalid pair: reset
|
||||
self.reset_payload();
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
// Another HIGH pulse while expecting LOW: update te_last
|
||||
self.te_last = duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MitsubishiV0Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
+312
-8
@@ -4,6 +4,13 @@
|
||||
//! Each decoder processes level+duration pairs from the demodulator and optionally supports
|
||||
//! encoding (replay). Shared pieces: [common], [keeloq_common], [keys], [aut64].
|
||||
//!
|
||||
//! **Decoder selection (vs ProtoPirate)**
|
||||
//! ProtoPirate calls `subghz_receiver_decode(receiver, level, duration)` for each pulse; the
|
||||
//! Flipper SDK receiver (not in REFERENCES) feeds all registered decoders. There is no
|
||||
//! preamble-based decoder selection in the scene—only the decoder's own feed() logic (e.g. VAG
|
||||
//! Reset/Preamble1/Preamble2). We do the same: feed every pulse to all decoders that support the
|
||||
//! file frequency; whoever returns a valid frame is reported. No extra preamble filtering.
|
||||
//!
|
||||
//! **Manchester decoding**: Ford, Fiat, and common each have separate Manchester state machines
|
||||
//! (FordV0ManchesterState, FiatV0ManchesterState, CommonManchesterState in common.rs). They are
|
||||
//! not reused across protocols. Event conventions match the reference per protocol (e.g. Kia V5
|
||||
@@ -12,6 +19,8 @@
|
||||
mod common;
|
||||
pub mod keeloq_common;
|
||||
mod keeloq;
|
||||
mod keeloq_barriers;
|
||||
pub use keeloq_barriers::is_keeloq_non_car;
|
||||
mod keeloq_generic;
|
||||
#[allow(dead_code)]
|
||||
pub mod aut64;
|
||||
@@ -23,14 +32,32 @@ mod kia_v2;
|
||||
mod kia_v3_v4;
|
||||
mod kia_v5;
|
||||
mod kia_v6;
|
||||
mod kia_v7;
|
||||
mod subaru;
|
||||
mod ford_v0;
|
||||
mod ford_v1;
|
||||
mod ford_v2;
|
||||
mod ford_v3;
|
||||
mod honda_static;
|
||||
mod honda_v1;
|
||||
mod vag;
|
||||
mod fiat_v0;
|
||||
mod fiat_v1;
|
||||
mod suzuki;
|
||||
mod scher_khan;
|
||||
mod star_line;
|
||||
mod psa;
|
||||
mod psa2;
|
||||
mod chrysler_v0;
|
||||
mod mazda_v0;
|
||||
mod mazda_siemens;
|
||||
mod mitsubishi_v0;
|
||||
mod porsche_touareg;
|
||||
mod porsche_cayenne;
|
||||
mod bmw_cas4;
|
||||
mod land_rover_v0;
|
||||
mod land_rover_rke;
|
||||
mod toyota;
|
||||
|
||||
pub use common::DecodedSignal;
|
||||
|
||||
@@ -95,16 +122,83 @@ impl ProtocolRegistry {
|
||||
Box::new(kia_v3_v4::KiaV3V4Decoder::new()),
|
||||
Box::new(kia_v5::KiaV5Decoder::new()),
|
||||
Box::new(kia_v6::KiaV6Decoder::new()),
|
||||
// Other protocols (Ford before Subaru so 250/500µs Ford keyfobs decode as Ford)
|
||||
Box::new(ford_v0::FordV0Decoder::new()),
|
||||
Box::new(subaru::SubaruDecoder::new()),
|
||||
Box::new(kia_v7::KiaV7Decoder::new()),
|
||||
// VAG before Ford/Subaru so 500/1000µs VAG streams decode as VAG (ProtoPirate order has VAG after Ford/Subaru but Flipper likely feeds all decoders; KAT uses first-match so VAG must be tried earlier)
|
||||
Box::new(vag::VagDecoder::new()),
|
||||
Box::new(ford_v0::FordV0Decoder::new()),
|
||||
// Ford V1: distinct 65/130µs Manchester (vs V0 250/500, V2 200/400, V3 240/480) and
|
||||
// CRC16-gated, so placement in the Ford group is safe (won't steal V0/V2/V3 captures).
|
||||
Box::new(ford_v1::FordV1Decoder::new()),
|
||||
Box::new(ford_v3::FordV3Decoder::new()),
|
||||
Box::new(ford_v2::FordV2Decoder::new()),
|
||||
// Honda Static after the Kia/Ford block; checksum-gated emission makes order safe.
|
||||
Box::new(honda_static::HondaStaticDecoder::new()),
|
||||
// Honda V1: distinct 1000/2000µs PWM (vs Honda Static's 63µs Manchester). Button-gated
|
||||
// emission makes order safe.
|
||||
Box::new(honda_v1::HondaV1Decoder::new()),
|
||||
Box::new(subaru::SubaruDecoder::new()),
|
||||
Box::new(fiat_v0::FiatV0Decoder::new()),
|
||||
Box::new(fiat_v1::FiatV1Decoder::new()),
|
||||
Box::new(suzuki::SuzukiDecoder::new()),
|
||||
Box::new(scher_khan::ScherKhanDecoder::new()),
|
||||
Box::new(star_line::StarLineDecoder::new()),
|
||||
Box::new(keeloq::KeeloqDecoder::new()),
|
||||
Box::new(psa::PsaDecoder::new()),
|
||||
// PSA2 (Flipper-ARF, internal name "PSA OLD"): the OLDER PSA variant. Manchester
|
||||
// 250/500µs (or 125/250µs half-rate), 128-bit frame (key1 64 + validation 16), AM/OOK,
|
||||
// TEA cipher with a mode23 XOR fast-path validated by a nibble checksum (the live
|
||||
// decoder runs ONLY this O(1) path — the dual TEA brute force is reserved for offline
|
||||
// decrypt and never runs in feed(), exactly as in the C). Placed AFTER `psa` so the
|
||||
// existing PSA keeps first-match priority and PSA2 acts as the older fallback. Emission
|
||||
// is gated on the XOR checksum (or validation nibble == 0xA), so it false-matches
|
||||
// nothing else.
|
||||
Box::new(psa2::Psa2Decoder::new()),
|
||||
// Chrysler V0 (300/3400-3700µs PWM, dual-long symbols). Timing is unique and emission
|
||||
// is gated on the frame's own structural check, so placement here is low-risk.
|
||||
Box::new(chrysler_v0::ChryslerV0Decoder::new()),
|
||||
// Land Rover V0 (differential Manchester 250/500µs, 81 bits). Emission is gated on a
|
||||
// 3-bit check polynomial + 16-bit tail + zero reserved bits, so placement is safe.
|
||||
Box::new(land_rover_v0::LandRoverV0Decoder::new()),
|
||||
// Land Rover RKE (Flipper-ARF): fixed-width PWM (700/300µs bits), 20-pulse preamble,
|
||||
// a distinctive 400µs+9600µs sync gap, and exactly 66 bits. KeeLoq hop is left
|
||||
// encrypted (no key), so emission is gated on the preamble + 9.6ms sync + 66-bit
|
||||
// strict-PWM geometry — unique among KAT protocols, so it false-matches nothing.
|
||||
Box::new(land_rover_rke::LandRoverRkeDecoder::new()),
|
||||
Box::new(mazda_v0::MazdaV0Decoder::new()),
|
||||
// Mazda Siemens (Flipper-ARF): the Siemens/VDO keyfob cipher. It rides the SAME
|
||||
// 250/500µs pair-based stream as Mazda V0 and uses the same additive-checksum gate,
|
||||
// so the two would match an identical set of frames. Placed AFTER mazda_v0 so Mazda
|
||||
// V0 keeps first-match priority and Mazda Siemens can never steal its captures.
|
||||
// Emission is gated on the structural preamble/sync/bit-count + checksum, so it does
|
||||
// not false-match other 250/500µs Manchester protocols. Adds an encoder (Mazda V0
|
||||
// has none).
|
||||
Box::new(mazda_siemens::MazdaSiemensDecoder::new()),
|
||||
// BMW CAS4 (Flipper-ARF): Manchester 500/1000µs, 64-bit, AM. The CAS4 rolling cipher's
|
||||
// manufacturer key is unavailable, so the payload is left encrypted — emission is gated
|
||||
// on the two fixed marker bytes (byte[0]==0x30 && byte[6]==0xC5), which is unique and
|
||||
// makes it false-match nothing. Decode-only (the reference encoder is a stub). Marker-
|
||||
// gated, so registry order here is safe.
|
||||
Box::new(bmw_cas4::BmwCas4Decoder::new()),
|
||||
Box::new(mitsubishi_v0::MitsubishiV0Decoder::new()),
|
||||
Box::new(porsche_touareg::PorscheTouaregDecoder::new()),
|
||||
// Porsche Cayenne (Flipper-ARF, internal name "Porsche AG"): the SAME wire protocol
|
||||
// as Porsche Touareg — Touareg is itself a port of this very porsche_cayenne.c source,
|
||||
// so they share the identical 1680/3370µs PWM, 73-pulse preamble + 5930µs gap, 64-bit
|
||||
// MSB-first frame, 24-bit VAG rotating-register cipher, and counter-recovery validity
|
||||
// gate. Placed AFTER porsche_touareg so Touareg keeps first-match priority and Cayenne
|
||||
// can NEVER steal a Touareg capture (the registry reports the first decoder that fires
|
||||
// on a given pulse). Cayenne adds the encoder (the Touareg port is decode-only) and is
|
||||
// additionally gated on the frame_type being one of the three values the C emits/labels
|
||||
// (0b001/0b010/0b100) — a strict subset of what Touareg accepts — so on any shared
|
||||
// frame Touareg also fires and wins by ordering.
|
||||
Box::new(porsche_cayenne::PorscheCayenneDecoder::new()),
|
||||
// Toyota/Lexus (Flipper-ARF): dual-variant KeeLoq. Variant A is KeeLoq-PWM at 433 MHz
|
||||
// and shares its air encoding with Kia V3/V4 — so Toyota MUST stay AFTER kia_v3_v4
|
||||
// (which is in the Kia block near the top) to preserve Kia's first-match priority on
|
||||
// the shared PWM frames. Toyota uniquely claims 60-bit frames and the Variant-B NRZ
|
||||
// stream at 315 MHz. Emission is gated on the exact frame bit count + structural
|
||||
// preamble/sync + non-zero serial, so it false-matches nothing else.
|
||||
Box::new(toyota::ToyotaDecoder::new()),
|
||||
];
|
||||
|
||||
Self { decoders }
|
||||
@@ -160,7 +254,9 @@ impl ProtocolRegistry {
|
||||
let level = if invert_level { !pair.level } else { pair.level };
|
||||
let duration_us = pair.duration_us;
|
||||
|
||||
let mut hit = None;
|
||||
// Feed this pulse to all decoders that support this frequency (Flipper-style).
|
||||
// Whoever actually produces a valid frame is reported; decoder order no longer decides.
|
||||
let mut hits: Vec<(String, DecodedSignal)> = Vec::new();
|
||||
for decoder in &mut self.decoders {
|
||||
let freq_supported = decoder
|
||||
.supported_frequencies()
|
||||
@@ -177,11 +273,10 @@ impl ProtocolRegistry {
|
||||
.protocol_display_name
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| decoder.name());
|
||||
hit = Some((name.to_string(), decoded));
|
||||
break;
|
||||
hits.push((name.to_string(), decoded));
|
||||
}
|
||||
}
|
||||
if let Some((name, decoded)) = hit {
|
||||
if let Some((name, decoded)) = hits.into_iter().next() {
|
||||
let segment: Vec<LevelDuration> = pairs[segment_start..=i]
|
||||
.iter()
|
||||
.map(|p| LevelDuration::new(p.level, p.duration_us))
|
||||
@@ -212,6 +307,8 @@ impl ProtocolRegistry {
|
||||
let level = if invert_level { !pair.level } else { pair.level };
|
||||
let duration_us = pair.duration_us;
|
||||
|
||||
// Feed this pulse to all decoders; report first valid frame (Flipper-style).
|
||||
let mut hits: Vec<(String, DecodedSignal)> = Vec::new();
|
||||
for decoder in &mut self.decoders {
|
||||
let freq_supported = decoder
|
||||
.supported_frequencies()
|
||||
@@ -230,9 +327,12 @@ impl ProtocolRegistry {
|
||||
.protocol_display_name
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| decoder.name());
|
||||
return Some((name.to_string(), decoded));
|
||||
hits.push((name.to_string(), decoded));
|
||||
}
|
||||
}
|
||||
if let Some((name, decoded)) = hits.into_iter().next() {
|
||||
return Some((name.to_string(), decoded));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
@@ -320,4 +420,208 @@ mod tests {
|
||||
results.iter().map(|(n, _, _)| n.as_str()).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ford_v3_decodes_ldv_t80_sub() {
|
||||
// LDV T80 keyfobs use a Ford-V3-style 240/480µs Manchester frame.
|
||||
let path = Path::new("IMPORTS/LDV/LDV-T80_lock.sub");
|
||||
if !path.exists() {
|
||||
eprintln!("Skip: {:?} not found (run from crate root)", path);
|
||||
return;
|
||||
}
|
||||
let (freq, raw_pairs) = import_sub_raw(path).unwrap();
|
||||
let pairs: Vec<LevelDuration> = raw_pairs
|
||||
.iter()
|
||||
.map(|p| LevelDuration::new(p.level, p.duration_us))
|
||||
.collect();
|
||||
let mut reg = ProtocolRegistry::new();
|
||||
let results = reg.process_signal_stream(&pairs, freq);
|
||||
assert!(
|
||||
results.iter().any(|(n, _, _)| n == "Ford V3"),
|
||||
"expected at least one Ford V3 decode from LDV-T80_lock.sub, got: {:?}",
|
||||
results.iter().map(|(n, _, _)| n.as_str()).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn honda_static_decodes_unlock_honda_sub() {
|
||||
// Genuine Honda keyfob capture (CC1101 custom preset). Honda Static unpacks it via the
|
||||
// reverse-packet path (matches honda_static.c), yielding a consistent serial/counter.
|
||||
let path = Path::new("IMPORTS/honda/Unlock_honda.sub");
|
||||
if !path.exists() {
|
||||
eprintln!("Skip: {:?} not found (run from crate root)", path);
|
||||
return;
|
||||
}
|
||||
let (freq, raw_pairs) = import_sub_raw(path).unwrap();
|
||||
let pairs: Vec<LevelDuration> = raw_pairs
|
||||
.iter()
|
||||
.map(|p| LevelDuration::new(p.level, p.duration_us))
|
||||
.collect();
|
||||
let mut reg = ProtocolRegistry::new();
|
||||
let results = reg.process_signal_stream(&pairs, freq);
|
||||
assert!(
|
||||
results.iter().any(|(n, _, _)| n == "Honda Static"),
|
||||
"expected at least one Honda Static decode from Unlock_honda.sub, got: {:?}",
|
||||
results.iter().map(|(n, _, _)| n.as_str()).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toyota_decodes_camry_variant_b_sub() {
|
||||
// 312 MHz Toyota Camry capture: Variant-B NRZ frame that Kia V3/V4 (and every other
|
||||
// decoder) rejects, so Toyota uniquely claims it. Guards the Toyota port + its registry
|
||||
// placement (after kia_v3_v4).
|
||||
let path = Path::new("IMPORTS/Toyota + Lexus/19_toyota_camry_l2_u2_t2_p2.sub");
|
||||
if !path.exists() {
|
||||
eprintln!("Skip: {:?} not found (run from crate root)", path);
|
||||
return;
|
||||
}
|
||||
let (freq, raw_pairs) = import_sub_raw(path).unwrap();
|
||||
let pairs: Vec<LevelDuration> = raw_pairs
|
||||
.iter()
|
||||
.map(|p| LevelDuration::new(p.level, p.duration_us))
|
||||
.collect();
|
||||
let mut reg = ProtocolRegistry::new();
|
||||
let results = reg.process_signal_stream(&pairs, freq);
|
||||
assert!(
|
||||
results.iter().any(|(n, _, _)| n == "Toyota"),
|
||||
"expected at least one Toyota decode from 19_toyota_camry_l2_u2_t2_p2.sub, got: {:?}",
|
||||
results.iter().map(|(n, _, _)| n.as_str()).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toyota_does_not_steal_kia_v3_v4_prius_sub() {
|
||||
// The Prius 433 MHz captures are KeeLoq-PWM frames whose air encoding is shared between
|
||||
// Toyota Variant A and Kia V3/V4. Kia V3/V4 is earlier in the registry and MUST keep
|
||||
// first-match priority — so these must still decode as "Kia V3/V4", never "Toyota".
|
||||
let path = Path::new("IMPORTS/Toyota + Lexus/Toyota_Prius2006_lock.sub");
|
||||
if !path.exists() {
|
||||
eprintln!("Skip: {:?} not found (run from crate root)", path);
|
||||
return;
|
||||
}
|
||||
let (freq, raw_pairs) = import_sub_raw(path).unwrap();
|
||||
let pairs: Vec<LevelDuration> = raw_pairs
|
||||
.iter()
|
||||
.map(|p| LevelDuration::new(p.level, p.duration_us))
|
||||
.collect();
|
||||
let mut reg = ProtocolRegistry::new();
|
||||
let results = reg.process_signal_stream(&pairs, freq);
|
||||
let names: Vec<&str> = results.iter().map(|(n, _, _)| n.as_str()).collect();
|
||||
assert!(
|
||||
names.iter().any(|n| *n == "Kia V3/V4"),
|
||||
"expected Kia V3/V4 decodes from Toyota_Prius2006_lock.sub, got: {:?}",
|
||||
names
|
||||
);
|
||||
assert!(
|
||||
!names.iter().any(|n| *n == "Toyota"),
|
||||
"Toyota must NOT steal the shared KeeLoq-PWM Prius frames from Kia V3/V4, got: {:?}",
|
||||
names
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn psa2_decodes_groupe_psa_sub() {
|
||||
// Genuine Peugeot/Citroën keyfob captures. PSA2 ("PSA OLD") decodes them via the mode23
|
||||
// XOR fast path (nibble-checksum gated), recovering a stable serial 0x99EB25 with a rolling
|
||||
// counter. Guards the PSA2 port + its registry placement (after `psa`).
|
||||
let path = Path::new("IMPORTS/GROUPE PSA/PSA_523_536.sub");
|
||||
if !path.exists() {
|
||||
eprintln!("Skip: {:?} not found (run from crate root)", path);
|
||||
return;
|
||||
}
|
||||
let (freq, raw_pairs) = import_sub_raw(path).unwrap();
|
||||
let pairs: Vec<LevelDuration> = raw_pairs
|
||||
.iter()
|
||||
.map(|p| LevelDuration::new(p.level, p.duration_us))
|
||||
.collect();
|
||||
let mut reg = ProtocolRegistry::new();
|
||||
let results = reg.process_signal_stream(&pairs, freq);
|
||||
let psa2: Vec<_> = results.iter().filter(|(n, _, _)| n == "PSA2").collect();
|
||||
assert!(
|
||||
!psa2.is_empty(),
|
||||
"expected at least one PSA2 decode from PSA_523_536.sub, got: {:?}",
|
||||
results.iter().map(|(n, _, _)| n.as_str()).collect::<Vec<_>>()
|
||||
);
|
||||
// The decode must carry recovered fields (serial), not a bare undecrypted frame.
|
||||
assert!(
|
||||
psa2.iter().any(|(_, d, _)| d.serial == Some(0x99EB25) && d.crc_valid),
|
||||
"expected a decrypted PSA2 frame with serial 0x99EB25, got serials: {:?}",
|
||||
psa2.iter().map(|(_, d, _)| d.serial).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn psa2_does_not_steal_vag_captures() {
|
||||
// PSA2 rides the same 250/500µs Manchester rate as several other protocols and the C's
|
||||
// mode23 nibble checksum is a loose ~1/16 gate. Emission is therefore restricted to a
|
||||
// field-bearing decrypt, so PSA2 must NOT match these VAG captures (which carry no valid
|
||||
// PSA2 frame) even though they reach 80 collectable bits at a PSA2-compatible frequency.
|
||||
for rel in ["VAG/Test_55_unlock_and_55_lock_suran.sub", "VAG/Vw pasat B7.sub"] {
|
||||
let path = Path::new("IMPORTS").join(rel);
|
||||
if !path.exists() {
|
||||
eprintln!("Skip: {:?} not found (run from crate root)", path);
|
||||
continue;
|
||||
}
|
||||
let (freq, raw_pairs) = import_sub_raw(&path).unwrap();
|
||||
let pairs: Vec<LevelDuration> = raw_pairs
|
||||
.iter()
|
||||
.map(|p| LevelDuration::new(p.level, p.duration_us))
|
||||
.collect();
|
||||
let mut reg = ProtocolRegistry::new();
|
||||
let results = reg.process_signal_stream(&pairs, freq);
|
||||
let names: Vec<&str> = results.iter().map(|(n, _, _)| n.as_str()).collect();
|
||||
assert!(
|
||||
!names.iter().any(|n| *n == "PSA2"),
|
||||
"PSA2 must not false-match {rel}, got: {names:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Diagnostic: sweep every IMPORTS/*.sub and print which protocol(s) each decodes as.
|
||||
/// `#[ignore]`d (noisy, always passes); run with
|
||||
/// `cargo test sweep_imports_decodes -- --ignored --nocapture`.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn sweep_imports_decodes() {
|
||||
use std::fs;
|
||||
let root = Path::new("IMPORTS");
|
||||
if !root.exists() {
|
||||
eprintln!("Skip: IMPORTS not found");
|
||||
return;
|
||||
}
|
||||
// Collect IMPORTS/<make>/*.sub
|
||||
let mut subs: Vec<std::path::PathBuf> = Vec::new();
|
||||
if let Ok(makes) = fs::read_dir(root) {
|
||||
for make in makes.flatten() {
|
||||
if make.path().is_dir() {
|
||||
if let Ok(files) = fs::read_dir(make.path()) {
|
||||
for f in files.flatten() {
|
||||
let p = f.path();
|
||||
if p.extension().map(|e| e == "sub").unwrap_or(false) {
|
||||
subs.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
subs.sort();
|
||||
let mut reg = ProtocolRegistry::new();
|
||||
for path in &subs {
|
||||
match import_sub_raw(path) {
|
||||
Ok((freq, raw_pairs)) => {
|
||||
let pairs: Vec<LevelDuration> = raw_pairs
|
||||
.iter()
|
||||
.map(|p| LevelDuration::new(p.level, p.duration_us))
|
||||
.collect();
|
||||
let results = reg.process_signal_stream(&pairs, freq);
|
||||
let names: Vec<&str> = results.iter().map(|(n, _, _)| n.as_str()).collect();
|
||||
let rel = path.strip_prefix("IMPORTS").unwrap_or(path);
|
||||
eprintln!("{:<42} {:>9}Hz => {:?}", rel.display(), freq, names);
|
||||
}
|
||||
Err(e) => eprintln!("{:<42} ERR {:?}", path.display(), e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,613 @@
|
||||
//! Porsche Cayenne protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with Flipper-ARF reference: `lib/subghz/protocols/porsche_cayenne.c`
|
||||
//! (internal protocol name in the firmware header is "Porsche AG").
|
||||
//!
|
||||
//! # Relationship to the existing `Porsche Touareg` decoder
|
||||
//!
|
||||
//! KAT already ships `porsche_touareg.rs`, which is itself a port of this very same
|
||||
//! `porsche_cayenne.c` source (its own doc comment and the `porsche_cayenne_compute_frame`
|
||||
//! function name make that explicit). The two protocols are therefore **the same wire
|
||||
//! protocol**: identical 1680/3370µs PWM, identical 73-pulse 3370µs preamble + 5930µs gap
|
||||
//! pair, identical 64-bit MSB-first frame, identical 24-bit rotating-register VAG cipher,
|
||||
//! and identical brute-force counter recovery / validity check (`counter != 0`).
|
||||
//!
|
||||
//! The only meaningful differences captured here versus Touareg:
|
||||
//! * Display name is `"Porsche Cayenne"` (vs `"Porsche Touareg"`).
|
||||
//! * This decoder ADDS the encoder from the C reference (the Touareg port is decode-only):
|
||||
//! a 4-frame burst (frame types 0b010/0b001/0b100/0b100), 73 sync pairs + gap pair +
|
||||
//! 64 MSB-first PWM data bits per frame.
|
||||
//!
|
||||
//! Because the two share an identical frame and validity gate, this decoder is registered
|
||||
//! AFTER `porsche_touareg` so Touareg keeps first-match priority and Cayenne can never steal
|
||||
//! a Touareg capture (the registry reports the first decoder that fires on a given pulse —
|
||||
//! see `process_signal_*_inner` in `mod.rs`). To keep Cayenne a faithful but strictly
|
||||
//! non-stealing decoder, emission is additionally gated on the frame_type being one of the
|
||||
//! three values the C only ever emits/labels (`0b001` Cont, `0b010` First, `0b100` Final).
|
||||
//! That is a strict subset of what Touareg accepts, so on any shared frame Touareg fires too
|
||||
//! and wins by ordering; Cayenne only ever fires on frames Touareg also accepts.
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - PWM bit pairs: SHORT LOW + LONG HIGH = 0, LONG LOW + SHORT HIGH = 1
|
||||
//! - 64 bits total; sync preamble of 15+ LOW/HIGH pairs at 3370µs, then 5930µs gap pair, then data
|
||||
//! - Field layout: pkt[0]=(btn<<4)|(frame_type&0x07), pkt[1..3]=serial 24-bit, pkt[4..7]=encrypted
|
||||
//! - Counter recovery via brute-force matching of computed encrypted bytes against received bytes
|
||||
//! - Frame types: 0x02="First", 0x01="Cont", 0x04="Final"
|
||||
//! - RF: AM/OOK. Frequencies: 433.92 MHz and 868.35 MHz (C flags 433|868)
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const TE_SHORT: u32 = 1680;
|
||||
const TE_LONG: u32 = 3370;
|
||||
const TE_DELTA: u32 = 500;
|
||||
const MIN_COUNT_BIT: usize = 64;
|
||||
|
||||
const PC_TE_SYNC: u32 = 3370;
|
||||
const PC_TE_GAP: u32 = 5930;
|
||||
const PC_SYNC_MIN: u16 = 15;
|
||||
/// Actual preamble pulse-pair count emitted by the firmware (PC_SYNC_COUNT).
|
||||
const PC_SYNC_COUNT: usize = 73;
|
||||
|
||||
/// KAT generic button codes (Lock=0x01, Unlock=0x02, Trunk=0x04, Panic=0x08).
|
||||
const BTN_LOCK: u8 = 0x01;
|
||||
const BTN_UNLOCK: u8 = 0x02;
|
||||
const BTN_TRUNK: u8 = 0x04;
|
||||
const BTN_PANIC: u8 = 0x08;
|
||||
|
||||
/// Decoder states (matches PCDecoderStep in porsche_cayenne.c).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Sync,
|
||||
GapHigh,
|
||||
GapLow,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// Porsche Cayenne protocol decoder/encoder.
|
||||
pub struct PorscheCayenneDecoder {
|
||||
step: DecoderStep,
|
||||
sync_count: u16,
|
||||
raw_data: u64,
|
||||
bit_count: usize,
|
||||
te_last: u32,
|
||||
}
|
||||
|
||||
/// Circular left-shift of a 24-bit register stored in three bytes (h, m, l).
|
||||
///
|
||||
/// Each byte shifts left by 1, receiving the MSB of the next byte in the chain:
|
||||
/// h gets MSB of m, m gets MSB of l, l gets MSB of h (wrap-around).
|
||||
///
|
||||
/// Matches the ROTATE24 macro in porsche_cayenne.c exactly.
|
||||
#[inline]
|
||||
fn rotate24(r_h: &mut u8, r_m: &mut u8, r_l: &mut u8) {
|
||||
let ch = (*r_h >> 7) & 1;
|
||||
let cm = (*r_m >> 7) & 1;
|
||||
let cl = (*r_l >> 7) & 1;
|
||||
*r_h = (*r_h << 1) | cm;
|
||||
*r_m = (*r_m << 1) | cl;
|
||||
*r_l = (*r_l << 1) | ch;
|
||||
}
|
||||
|
||||
/// Compute an 8-byte frame from serial, button, counter, and frame_type.
|
||||
///
|
||||
/// Direct port of `porsche_cayenne_compute_frame` from the C reference. The cipher
|
||||
/// increments `counter` by 1 internally. pkt[0..3] = plaintext header, pkt[4..7] = cipher
|
||||
/// output derived from a 24-bit rotate register seeded from serial bytes and rotated
|
||||
/// (4 + counter_low) times.
|
||||
fn compute_frame(serial24: u32, btn: u8, counter: u16, frame_type: u8) -> [u8; 8] {
|
||||
let b0 = (btn << 4) | (frame_type & 0x07);
|
||||
let b1 = ((serial24 >> 16) & 0xFF) as u8;
|
||||
let b2 = ((serial24 >> 8) & 0xFF) as u8;
|
||||
let b3 = (serial24 & 0xFF) as u8;
|
||||
|
||||
// Internal counter increment (firmware @ 0x14122).
|
||||
let cnt = counter.wrapping_add(1);
|
||||
let cnt_lo = (cnt & 0xFF) as u8;
|
||||
let cnt_hi = ((cnt >> 8) & 0xFF) as u8;
|
||||
|
||||
// Seed 24-bit register: r_h <- serial LSB, r_m <- serial MSB, r_l <- serial mid.
|
||||
let mut r_h = b3;
|
||||
let mut r_m = b1;
|
||||
let mut r_l = b2;
|
||||
|
||||
// Loop 1: 4 fixed rotations.
|
||||
for _ in 0..4 {
|
||||
rotate24(&mut r_h, &mut r_m, &mut r_l);
|
||||
}
|
||||
// Loop 2: cnt_lo additional rotations.
|
||||
for _ in 0..cnt_lo as u16 {
|
||||
rotate24(&mut r_h, &mut r_m, &mut r_l);
|
||||
}
|
||||
|
||||
// 9A: XOR of r_h with base byte.
|
||||
let a9a = r_h ^ b0;
|
||||
|
||||
// 9B: three masked slices of (~cnt_lo / ~cnt_hi) XOR r_m.
|
||||
let nb9b_p1 = ((!cnt_lo).wrapping_shl(2) & 0xFC) ^ r_m;
|
||||
let nb9b_p2 = ((!cnt_hi).wrapping_shl(2) & 0xFC) ^ r_m;
|
||||
let nb9b_p3 = ((!cnt_hi).wrapping_shr(6) & 0x03) ^ r_m;
|
||||
let a9b = (nb9b_p1 & 0xCC) | (nb9b_p2 & 0x30) | (nb9b_p3 & 0x03);
|
||||
|
||||
// 9C: three masked slices of (~cnt_lo / ~cnt_hi) XOR r_l.
|
||||
let nb9c_p1 = ((!cnt_lo).wrapping_shr(2) & 0x3F) ^ r_l;
|
||||
let nb9c_p2 = ((!cnt_hi & 0x03).wrapping_shl(6)) ^ r_l;
|
||||
let nb9c_p3 = ((!cnt_hi).wrapping_shr(2) & 0x3F) ^ r_l;
|
||||
let a9c = (nb9c_p1 & 0x33) | (nb9c_p2 & 0xC0) | (nb9c_p3 & 0x0C);
|
||||
|
||||
let mut pkt = [0u8; 8];
|
||||
pkt[0] = b0;
|
||||
pkt[1] = b1;
|
||||
pkt[2] = b2;
|
||||
pkt[3] = b3;
|
||||
pkt[4] = ((a9a >> 2) & 0x3F) | ((!cnt_lo & 0x03) << 6);
|
||||
pkt[5] = (!cnt_lo & 0xC0) | ((a9a & 0x03) << 4) | (a9b & 0x0C) | ((!cnt_lo).wrapping_shr(2) & 0x03);
|
||||
pkt[6] = ((a9b & 0x03) << 6) | ((a9c >> 2) & 0x3C) | ((!cnt_lo).wrapping_shr(4) & 0x03);
|
||||
pkt[7] = ((a9b >> 4) & 0x0F) | ((a9c & 0x0F) << 4);
|
||||
|
||||
pkt
|
||||
}
|
||||
|
||||
/// Unpack a raw 64-bit frame into its 8 bytes (big-endian: pkt[0] is the MSB).
|
||||
fn unpack_bytes(data: u64) -> [u8; 8] {
|
||||
let mut pkt = [0u8; 8];
|
||||
let mut raw = data;
|
||||
for i in (0..8).rev() {
|
||||
pkt[i] = (raw & 0xFF) as u8;
|
||||
raw >>= 8;
|
||||
}
|
||||
pkt
|
||||
}
|
||||
|
||||
/// Brute-force counter recovery: try counter values 1..=256 (matching the C loop) and
|
||||
/// compare the recomputed cipher bytes pkt[4..7]. Returns 0 if no match (invalid frame).
|
||||
fn recover_counter(serial: u32, btn: u8, frame_type: u8, pkt: &[u8; 8]) -> u16 {
|
||||
for try_cnt in 1u16..=256 {
|
||||
// The cipher increments internally, so pass try_cnt - 1.
|
||||
let try_pkt = compute_frame(serial, btn, try_cnt - 1, frame_type);
|
||||
if try_pkt[4] == pkt[4]
|
||||
&& try_pkt[5] == pkt[5]
|
||||
&& try_pkt[6] == pkt[6]
|
||||
&& try_pkt[7] == pkt[7]
|
||||
{
|
||||
return try_cnt;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// Parse raw 64-bit data into a DecodedSignal, or `None` if it is not a valid Cayenne frame.
|
||||
///
|
||||
/// Cayenne-specific gate (keeps this decoder from stealing Touareg captures): the frame_type
|
||||
/// must be one of the three values the C reference emits/labels (0b001 Cont, 0b010 First,
|
||||
/// 0b100 Final), AND the counter must be recoverable (cipher-consistent, `counter != 0`).
|
||||
fn parse_data(data: u64) -> Option<DecodedSignal> {
|
||||
let pkt = unpack_bytes(data);
|
||||
|
||||
let serial = ((pkt[1] as u32) << 16) | ((pkt[2] as u32) << 8) | (pkt[3] as u32);
|
||||
let btn = pkt[0] >> 4;
|
||||
let frame_type = pkt[0] & 0x07;
|
||||
|
||||
// Cayenne-specific invariant: only the three documented frame types.
|
||||
let frame_type_name = match frame_type {
|
||||
0b010 => "First",
|
||||
0b001 => "Cont",
|
||||
0b100 => "Final",
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// Cipher consistency check (also the C's validity signal).
|
||||
let counter = recover_counter(serial, btn, frame_type, &pkt);
|
||||
if counter == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(btn),
|
||||
counter: Some(counter),
|
||||
crc_valid: true, // cipher-consistent frame
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: Some(frame_type as u64),
|
||||
protocol_display_name: Some(format!("Porsche Cayenne [{}]", frame_type_name)),
|
||||
})
|
||||
}
|
||||
|
||||
impl PorscheCayenneDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
sync_count: 0,
|
||||
raw_data: 0,
|
||||
bit_count: 0,
|
||||
te_last: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a KAT generic button command to a Porsche Cayenne / VAG 4-bit button code.
|
||||
/// KAT: Lock=0x01, Unlock=0x02, Trunk=0x04, Panic=0x08.
|
||||
/// The C `porsche_cayenne_get_btn_code` maps d-pad: Up=0x01 Lock, Down=0x02 Unlock,
|
||||
/// Left=0x04 Trunk, Right=0x08 Open — an identical set, so the codes pass through.
|
||||
fn map_button(button: u8) -> u8 {
|
||||
match button {
|
||||
BTN_LOCK | BTN_UNLOCK | BTN_TRUNK | BTN_PANIC => button & 0x0F,
|
||||
b => b & 0x0F,
|
||||
}
|
||||
}
|
||||
|
||||
/// Append one PWM data bit as a (LOW, HIGH) pair, matching the C encoder:
|
||||
/// bit 0: SHORT LOW + LONG HIGH; bit 1: LONG LOW + SHORT HIGH.
|
||||
fn push_bit(signal: &mut Vec<LevelDuration>, bit: bool) {
|
||||
if bit {
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit one full frame (73 sync pairs + gap pair + 64 MSB-first data bits) into `signal`.
|
||||
/// Matches `porsche_cayenne_build_upload`'s per-frame body.
|
||||
fn push_frame(signal: &mut Vec<LevelDuration>, pkt: &[u8; 8]) {
|
||||
// Preamble: 73 × (LOW LONG + HIGH LONG).
|
||||
for _ in 0..PC_SYNC_COUNT {
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
}
|
||||
// Gap: LOW GAP + HIGH GAP.
|
||||
signal.push(LevelDuration::new(false, PC_TE_GAP));
|
||||
signal.push(LevelDuration::new(true, PC_TE_GAP));
|
||||
// 64 data bits, MSB first, byte order pkt[0]->pkt[7].
|
||||
for &byte in pkt.iter() {
|
||||
for bit in (0..8).rev() {
|
||||
Self::push_bit(signal, (byte >> bit) & 1 != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for PorscheCayenneDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Porsche Cayenne"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: MIN_COUNT_BIT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
// C flags: SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868.
|
||||
&[433_920_000, 868_350_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.sync_count = 0;
|
||||
self.raw_data = 0;
|
||||
self.bit_count = 0;
|
||||
self.te_last = 0;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
// Reset: wait for a LOW pulse matching sync timing (3370µs).
|
||||
DecoderStep::Reset => {
|
||||
if !level && duration_diff!(duration, PC_TE_SYNC) < TE_DELTA {
|
||||
self.sync_count = 1;
|
||||
self.step = DecoderStep::Sync;
|
||||
}
|
||||
}
|
||||
|
||||
// Sync: count sync pulses (HIGH and LOW at 3370µs).
|
||||
// On a gap pulse (5930µs) with enough sync pulses, transition to GapHigh/GapLow.
|
||||
DecoderStep::Sync => {
|
||||
if level {
|
||||
if duration_diff!(duration, PC_TE_SYNC) < TE_DELTA {
|
||||
// Good sync HIGH — keep collecting.
|
||||
} else if self.sync_count >= PC_SYNC_MIN
|
||||
&& duration_diff!(duration, PC_TE_GAP) < TE_DELTA
|
||||
{
|
||||
// HIGH gap after sufficient sync pulses.
|
||||
self.step = DecoderStep::GapLow;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
// LOW pulse.
|
||||
if duration_diff!(duration, PC_TE_SYNC) < TE_DELTA {
|
||||
self.sync_count += 1;
|
||||
} else if self.sync_count >= PC_SYNC_MIN
|
||||
&& duration_diff!(duration, PC_TE_GAP) < TE_DELTA
|
||||
{
|
||||
// LOW gap after sufficient sync pulses.
|
||||
self.step = DecoderStep::GapHigh;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GapHigh: expect the complementary HIGH gap pulse.
|
||||
DecoderStep::GapHigh => {
|
||||
if level && duration_diff!(duration, PC_TE_GAP) < TE_DELTA {
|
||||
self.raw_data = 0;
|
||||
self.bit_count = 0;
|
||||
self.step = DecoderStep::Data;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
// GapLow: expect the complementary LOW gap pulse.
|
||||
DecoderStep::GapLow => {
|
||||
if !level && duration_diff!(duration, PC_TE_GAP) < TE_DELTA {
|
||||
self.raw_data = 0;
|
||||
self.bit_count = 0;
|
||||
self.step = DecoderStep::Data;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
// Data: decode bit pairs.
|
||||
// LOW pulses are saved in te_last; HIGH pulses complete the bit:
|
||||
// SHORT LOW + LONG HIGH = bit 0
|
||||
// LONG LOW + SHORT HIGH = bit 1
|
||||
DecoderStep::Data => {
|
||||
if level {
|
||||
let bit_value;
|
||||
if duration_diff!(self.te_last, TE_SHORT) < TE_DELTA
|
||||
&& duration_diff!(duration, TE_LONG) < TE_DELTA
|
||||
{
|
||||
bit_value = false; // bit 0
|
||||
} else if duration_diff!(self.te_last, TE_LONG) < TE_DELTA
|
||||
&& duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||
{
|
||||
bit_value = true; // bit 1
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
return None;
|
||||
}
|
||||
|
||||
self.raw_data = (self.raw_data << 1) | (bit_value as u64);
|
||||
self.bit_count += 1;
|
||||
|
||||
if self.bit_count >= MIN_COUNT_BIT {
|
||||
let data = self.raw_data;
|
||||
self.step = DecoderStep::Reset;
|
||||
// parse_data returns None if the Cayenne gate fails (not a Cayenne
|
||||
// frame), so this decoder stays silent on those and never steals them.
|
||||
return parse_data(data);
|
||||
}
|
||||
} else {
|
||||
// LOW pulse: save duration for the bit pair.
|
||||
self.te_last = duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let serial = decoded.serial? & 0xFFFFFF;
|
||||
let cnt = decoded.counter.unwrap_or(0);
|
||||
let btn = Self::map_button(button);
|
||||
|
||||
// 4-frame burst (matches porsche_cayenne_build_upload):
|
||||
// Frame 0: frame_type=0b010, cipher counter = cnt+1
|
||||
// Frame 1: frame_type=0b001, cipher counter = cnt+2
|
||||
// Frame 2: frame_type=0b100, cipher counter = cnt+3
|
||||
// Frame 3: frame_type=0b100, cipher counter = cnt+4
|
||||
// (compute_frame increments the passed counter by 1 internally.)
|
||||
const FRAME_TYPES: [u8; 4] = [0b010, 0b001, 0b100, 0b100];
|
||||
|
||||
// Per-frame size: 73 sync pairs + 1 gap pair + 64 bit pairs = (73 + 1 + 64) * 2.
|
||||
let mut signal = Vec::with_capacity((PC_SYNC_COUNT + 1 + 64) * 2 * 4);
|
||||
|
||||
for (f, &ft) in FRAME_TYPES.iter().enumerate() {
|
||||
let pkt = compute_frame(serial, btn, cnt.wrapping_add(f as u16), ft);
|
||||
Self::push_frame(&mut signal, &pkt);
|
||||
}
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PorscheCayenneDecoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Feed an encoded signal back through a fresh decoder and return the first decode.
|
||||
fn decode_signal(signal: &[LevelDuration]) -> Option<DecodedSignal> {
|
||||
let mut dec = PorscheCayenneDecoder::new();
|
||||
for ld in signal {
|
||||
if let Some(d) = dec.feed(ld.level, ld.duration_us) {
|
||||
return Some(d);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Build a one-frame on-air signal for a single (serial, btn, counter, frame_type) and
|
||||
/// decode it back. The frame's cipher counter is `counter+1` (compute_frame increments),
|
||||
/// and the decoder's brute-force recovery reports that incremented value.
|
||||
fn roundtrip_single(serial: u32, btn: u8, counter: u16, frame_type: u8) -> DecodedSignal {
|
||||
let pkt = compute_frame(serial, btn, counter, frame_type);
|
||||
let mut signal = Vec::new();
|
||||
PorscheCayenneDecoder::push_frame(&mut signal, &pkt);
|
||||
decode_signal(&signal)
|
||||
.unwrap_or_else(|| panic!("decode failed for serial {serial:#X} btn {btn:#X} cnt {counter} ft {frame_type:#b}"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_roundtrip_via_encoder() {
|
||||
// Full encoder path: encode() emits a 4-frame burst; the first frame (type 0b010,
|
||||
// cipher counter = cnt+1) must decode back with the right serial/button, and the
|
||||
// recovered counter must survive the rolling cipher (== cnt+1).
|
||||
let serials = [0x00ABCDEFu32, 0x00123456, 0x00000001, 0x00FFFFFE, 0x005A5A5A];
|
||||
let buttons = [BTN_LOCK, BTN_UNLOCK, BTN_TRUNK, BTN_PANIC];
|
||||
// Counters kept within the C reference's recoverable range: its decoder only
|
||||
// brute-forces cnt_lo (256 values), so the first burst frame's cipher counter
|
||||
// (seed+1) must be <= 256, i.e. seed <= 255. See `counter_recovery_limit_matches_c`.
|
||||
let counters = [0u16, 1, 42, 200, 254, 255];
|
||||
|
||||
for (&serial, &counter) in serials.iter().zip(counters.iter()) {
|
||||
for &btn in &buttons {
|
||||
let seed = DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(btn),
|
||||
counter: Some(counter),
|
||||
crc_valid: true,
|
||||
data: 0,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
};
|
||||
let encoder = PorscheCayenneDecoder::new();
|
||||
let signal = encoder.encode(&seed, btn).expect("encode should succeed");
|
||||
let decoded = decode_signal(&signal).unwrap_or_else(|| {
|
||||
panic!("decode failed for serial {serial:#X} btn {btn:#X} cnt {counter}")
|
||||
});
|
||||
|
||||
let expected_btn = PorscheCayenneDecoder::map_button(btn);
|
||||
assert_eq!(decoded.serial, Some(serial & 0xFFFFFF), "serial");
|
||||
assert_eq!(decoded.button, Some(expected_btn), "button");
|
||||
// First frame's cipher counter is cnt+1 (compute_frame increments).
|
||||
assert_eq!(
|
||||
decoded.counter,
|
||||
Some(counter.wrapping_add(1)),
|
||||
"counter must survive the rolling cipher"
|
||||
);
|
||||
assert_eq!(decoded.data_count_bit, MIN_COUNT_BIT, "bit count");
|
||||
assert!(decoded.crc_valid, "cipher-consistent frame → crc_valid");
|
||||
// First burst frame is type 0b010 = "First".
|
||||
assert_eq!(decoded.extra, Some(0b010), "frame_type First");
|
||||
assert_eq!(
|
||||
decoded.protocol_display_name.as_deref(),
|
||||
Some("Porsche Cayenne [First]")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_all_frame_types() {
|
||||
// Each of the three documented frame types decodes and labels correctly, and the
|
||||
// counter survives across the cipher for a spread of counter values.
|
||||
let cases = [
|
||||
(0b010u8, "Porsche Cayenne [First]"),
|
||||
(0b001u8, "Porsche Cayenne [Cont]"),
|
||||
(0b100u8, "Porsche Cayenne [Final]"),
|
||||
];
|
||||
for &(ft, name) in &cases {
|
||||
// Counters within the C's recoverable cnt_lo range (cipher counter seed+1 <= 256).
|
||||
for &counter in &[0u16, 7, 200, 254, 255] {
|
||||
let d = roundtrip_single(0x00C0FFEE, BTN_UNLOCK, counter, ft);
|
||||
assert_eq!(d.serial, Some(0x00C0FFEE), "serial ft={ft:#b}");
|
||||
assert_eq!(d.button, Some(BTN_UNLOCK), "button ft={ft:#b}");
|
||||
assert_eq!(
|
||||
d.counter,
|
||||
Some(counter.wrapping_add(1)),
|
||||
"counter survives cipher ft={ft:#b} cnt={counter}"
|
||||
);
|
||||
assert_eq!(d.extra, Some(ft as u64), "frame_type ft={ft:#b}");
|
||||
assert_eq!(d.protocol_display_name.as_deref(), Some(name));
|
||||
assert!(d.crc_valid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_undocumented_frame_type() {
|
||||
// A structurally valid PWM frame whose frame_type is NOT one of {0b001,0b010,0b100}
|
||||
// must be rejected by Cayenne's gate — this is what keeps it from stealing arbitrary
|
||||
// 1680/3370µs PWM frames (and what makes Touareg, ordered first, the owner of any
|
||||
// shared frame). frame_type 0b011 is unused by the C reference.
|
||||
let pkt = compute_frame(0x00123456, BTN_LOCK, 5, 0b011);
|
||||
let mut signal = Vec::new();
|
||||
PorscheCayenneDecoder::push_frame(&mut signal, &pkt);
|
||||
assert!(
|
||||
decode_signal(&signal).is_none(),
|
||||
"undocumented frame_type 0b011 must not decode as Cayenne"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_truncated_frame() {
|
||||
// 63 of 64 data bits must not decode.
|
||||
let pkt = compute_frame(0x00ABCDEF, BTN_LOCK, 3, 0b010);
|
||||
let mut signal = Vec::new();
|
||||
PorscheCayenneDecoder::push_frame(&mut signal, &pkt);
|
||||
// Frame = 73 sync pairs + 1 gap pair + 64 bit pairs. Drop the last bit pair.
|
||||
let keep = (PC_SYNC_COUNT + 1 + 63) * 2;
|
||||
assert!(
|
||||
decode_signal(&signal[..keep]).is_none(),
|
||||
"63-bit frame must not decode"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn counter_recovery_limit_matches_c() {
|
||||
// Faithful limitation of the C reference: its decoder brute-forces only cnt_lo
|
||||
// (try_cnt 1..=256), so frames whose cipher counter (passed counter + 1) exceeds 256
|
||||
// cannot have their counter recovered. recover_counter returns 0 for those, and the
|
||||
// frame is reported invalid (does not decode) — exactly as the C would behave.
|
||||
// seed=255 -> cipher counter 256 -> recoverable; seed=256 -> 257 -> NOT recoverable.
|
||||
let pkt_ok = compute_frame(0x00ABCDEF, BTN_LOCK, 255, 0b010);
|
||||
assert_eq!(
|
||||
recover_counter(0x00ABCDEF, BTN_LOCK, 0b010, &pkt_ok),
|
||||
256,
|
||||
"cipher counter 256 is the highest recoverable value"
|
||||
);
|
||||
let pkt_oob = compute_frame(0x00ABCDEF, BTN_LOCK, 256, 0b010);
|
||||
assert_eq!(
|
||||
recover_counter(0x00ABCDEF, BTN_LOCK, 0b010, &pkt_oob),
|
||||
0,
|
||||
"cipher counter 257 is beyond the C's cnt_lo brute-force range"
|
||||
);
|
||||
// And such an out-of-range frame must not decode (parse_data rejects counter==0).
|
||||
let mut signal = Vec::new();
|
||||
PorscheCayenneDecoder::push_frame(&mut signal, &pkt_oob);
|
||||
assert!(
|
||||
decode_signal(&signal).is_none(),
|
||||
"frame with unrecoverable counter must not decode (matches C)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cipher_matches_touareg_reference_vectors() {
|
||||
// The cipher/frame is shared with the Touareg port (same C source). Spot-check that
|
||||
// compute_frame -> unpack header bytes are exactly as laid out by the C reference:
|
||||
// pkt[0]=(btn<<4)|ft, pkt[1..3]=serial big-endian.
|
||||
let pkt = compute_frame(0x00ABCDEF, 0x02, 0x10, 0b010);
|
||||
assert_eq!(pkt[0], (0x02 << 4) | 0b010, "pkt[0] = (btn<<4)|ft");
|
||||
assert_eq!(pkt[1], 0xAB, "serial MSB");
|
||||
assert_eq!(pkt[2], 0xCD, "serial mid");
|
||||
assert_eq!(pkt[3], 0xEF, "serial LSB");
|
||||
// Recovering the counter from this exact frame yields cnt+1 = 0x11.
|
||||
let cnt = recover_counter(0x00ABCDEF, 0x02, 0b010, &pkt);
|
||||
assert_eq!(cnt, 0x11, "brute-force counter recovery = passed counter + 1");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
//! Porsche Touareg protocol decoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/porsche_touareg.c`.
|
||||
//! Original implementation by @lupettohf
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - PWM encoding with Manchester-like bit pairs: SHORT LOW + LONG HIGH = 0, LONG LOW + SHORT HIGH = 1
|
||||
//! - 64 bits total; sync preamble of 15+ LOW/HIGH pairs at 3370µs, then 5930µs gap pair, then data
|
||||
//! - Field layout: pkt[0]=(btn<<4)|(frame_type&0x07), pkt[1..3]=serial 24-bit, pkt[4..7]=encrypted
|
||||
//! - Counter recovery via brute-force matching of computed encrypted bytes against received bytes
|
||||
//! - Frame types: 0x02="First", 0x01="Cont", 0x04="Final"
|
||||
//! - Frequencies: 433.92 MHz and 868.35 MHz (AM demodulation)
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const TE_SHORT: u32 = 1680;
|
||||
const TE_LONG: u32 = 3370;
|
||||
const TE_DELTA: u32 = 500;
|
||||
const MIN_COUNT_BIT: usize = 64;
|
||||
|
||||
const PC_TE_SYNC: u32 = 3370;
|
||||
const PC_TE_GAP: u32 = 5930;
|
||||
const PC_SYNC_MIN: u16 = 15;
|
||||
|
||||
/// Decoder states (matches PorscheCayenneDecoderStep in porsche_touareg.c)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Sync,
|
||||
GapHigh,
|
||||
GapLow,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// Porsche Touareg protocol decoder
|
||||
pub struct PorscheTouaregDecoder {
|
||||
step: DecoderStep,
|
||||
sync_count: u16,
|
||||
raw_data: u64,
|
||||
bit_count: usize,
|
||||
te_last: u32,
|
||||
}
|
||||
|
||||
/// Circular left-shift of a 24-bit register stored in three bytes (h, m, l).
|
||||
///
|
||||
/// Each byte shifts left by 1, receiving the MSB of the next byte in the chain:
|
||||
/// h gets MSB of m, m gets MSB of l, l gets MSB of h (wrap-around).
|
||||
///
|
||||
/// Matches the ROTATE24 macro in porsche_touareg.c exactly.
|
||||
#[inline]
|
||||
fn rotate24(r_h: &mut u8, r_m: &mut u8, r_l: &mut u8) {
|
||||
let ch = (*r_h >> 7) & 1;
|
||||
let cm = (*r_m >> 7) & 1;
|
||||
let cl = (*r_l >> 7) & 1;
|
||||
*r_h = (*r_h << 1) | cm;
|
||||
*r_m = (*r_m << 1) | cl;
|
||||
*r_l = (*r_l << 1) | ch;
|
||||
}
|
||||
|
||||
/// Compute an 8-byte frame from serial, button, counter, and frame_type.
|
||||
///
|
||||
/// This is a direct port of `porsche_cayenne_compute_frame` from the C reference.
|
||||
/// pkt[0..3] = plaintext header, pkt[4..7] = encrypted payload derived from
|
||||
/// a 24-bit rotate register seeded from serial bytes and rotated (4 + counter_low) times.
|
||||
fn compute_frame(serial24: u32, btn: u8, counter: u16, frame_type: u8) -> [u8; 8] {
|
||||
let b0 = (btn << 4) | (frame_type & 0x07);
|
||||
let b1 = ((serial24 >> 16) & 0xFF) as u8;
|
||||
let b2 = ((serial24 >> 8) & 0xFF) as u8;
|
||||
let b3 = (serial24 & 0xFF) as u8;
|
||||
|
||||
let cnt = counter.wrapping_add(1);
|
||||
let cnt_lo = (cnt & 0xFF) as u8;
|
||||
let cnt_hi = ((cnt >> 8) & 0xFF) as u8;
|
||||
|
||||
let mut r_h = b3;
|
||||
let mut r_m = b1;
|
||||
let mut r_l = b2;
|
||||
|
||||
// Rotate 4 times initially
|
||||
for _ in 0..4 {
|
||||
rotate24(&mut r_h, &mut r_m, &mut r_l);
|
||||
}
|
||||
// Then rotate cnt_lo more times
|
||||
for _ in 0..cnt_lo as u16 {
|
||||
rotate24(&mut r_h, &mut r_m, &mut r_l);
|
||||
}
|
||||
|
||||
let a9a = r_h ^ b0;
|
||||
|
||||
let nb9b_p1 = ((!cnt_lo).wrapping_shl(2) & 0xFC) ^ r_m;
|
||||
let nb9b_p2 = ((!cnt_hi).wrapping_shl(2) & 0xFC) ^ r_m;
|
||||
let nb9b_p3 = ((!cnt_hi).wrapping_shr(6) & 0x03) ^ r_m;
|
||||
let a9b = (nb9b_p1 & 0xCC) | (nb9b_p2 & 0x30) | (nb9b_p3 & 0x03);
|
||||
|
||||
let nb9c_p1 = ((!cnt_lo).wrapping_shr(2) & 0x3F) ^ r_l;
|
||||
let nb9c_p2 = ((!cnt_hi & 0x03).wrapping_shl(6)) ^ r_l;
|
||||
let nb9c_p3 = ((!cnt_hi).wrapping_shr(2) & 0x3F) ^ r_l;
|
||||
let a9c = (nb9c_p1 & 0x33) | (nb9c_p2 & 0xC0) | (nb9c_p3 & 0x0C);
|
||||
|
||||
let mut pkt = [0u8; 8];
|
||||
pkt[0] = b0;
|
||||
pkt[1] = b1;
|
||||
pkt[2] = b2;
|
||||
pkt[3] = b3;
|
||||
pkt[4] = ((a9a >> 2) & 0x3F) | ((!cnt_lo & 0x03) << 6);
|
||||
pkt[5] = (!cnt_lo & 0xC0) | ((a9a & 0x03) << 4) | (a9b & 0x0C) | ((!cnt_lo).wrapping_shr(2) & 0x03);
|
||||
pkt[6] = ((a9b & 0x03) << 6) | ((a9c >> 2) & 0x3C) | ((!cnt_lo).wrapping_shr(4) & 0x03);
|
||||
pkt[7] = ((a9b >> 4) & 0x0F) | ((a9c & 0x0F) << 4);
|
||||
|
||||
pkt
|
||||
}
|
||||
|
||||
/// Parse raw 64-bit data into a DecodedSignal.
|
||||
///
|
||||
/// Extracts serial (24-bit), button (4-bit), frame_type (3-bit), then brute-forces
|
||||
/// the counter (1..=256) by calling compute_frame and comparing encrypted bytes.
|
||||
fn parse_data(data: u64) -> DecodedSignal {
|
||||
// Unpack 64-bit data into 8 bytes (big-endian)
|
||||
let mut pkt = [0u8; 8];
|
||||
let mut raw = data;
|
||||
for i in (0..8).rev() {
|
||||
pkt[i] = (raw & 0xFF) as u8;
|
||||
raw >>= 8;
|
||||
}
|
||||
|
||||
let serial = ((pkt[1] as u32) << 16) | ((pkt[2] as u32) << 8) | (pkt[3] as u32);
|
||||
let btn = pkt[0] >> 4;
|
||||
let frame_type = pkt[0] & 0x07;
|
||||
|
||||
// Brute-force counter recovery: try counter values 1..=256
|
||||
let mut counter: u16 = 0;
|
||||
for try_cnt in 1u16..=256 {
|
||||
let try_pkt = compute_frame(serial, btn, try_cnt - 1, frame_type);
|
||||
if try_pkt[4] == pkt[4]
|
||||
&& try_pkt[5] == pkt[5]
|
||||
&& try_pkt[6] == pkt[6]
|
||||
&& try_pkt[7] == pkt[7]
|
||||
{
|
||||
counter = try_cnt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine frame type name for extra info
|
||||
let frame_type_name = match frame_type {
|
||||
0x02 => "First",
|
||||
0x01 => "Cont",
|
||||
0x04 => "Final",
|
||||
_ => "??",
|
||||
};
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(btn),
|
||||
counter: Some(counter),
|
||||
crc_valid: counter != 0,
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: false,
|
||||
extra: Some(frame_type as u64),
|
||||
protocol_display_name: Some(format!(
|
||||
"Porsche Touareg [{}]",
|
||||
frame_type_name
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
impl PorscheTouaregDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
sync_count: 0,
|
||||
raw_data: 0,
|
||||
bit_count: 0,
|
||||
te_last: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for PorscheTouaregDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Porsche Touareg"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: MIN_COUNT_BIT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[433_920_000, 868_350_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.sync_count = 0;
|
||||
self.raw_data = 0;
|
||||
self.bit_count = 0;
|
||||
self.te_last = 0;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
// Reset: wait for a LOW pulse matching sync timing (3370µs)
|
||||
DecoderStep::Reset => {
|
||||
if !level && duration_diff!(duration, PC_TE_SYNC) < TE_DELTA {
|
||||
self.sync_count = 1;
|
||||
self.step = DecoderStep::Sync;
|
||||
}
|
||||
}
|
||||
|
||||
// Sync: count sync pulses (HIGH and LOW at 3370µs).
|
||||
// On a gap pulse (5930µs) with enough sync pulses, transition to GapHigh/GapLow.
|
||||
DecoderStep::Sync => {
|
||||
if level {
|
||||
if duration_diff!(duration, PC_TE_SYNC) < TE_DELTA {
|
||||
// Keep collecting sync pairs -- HIGH sync pulse, stay in Sync
|
||||
} else if self.sync_count >= PC_SYNC_MIN
|
||||
&& duration_diff!(duration, PC_TE_GAP) < TE_DELTA
|
||||
{
|
||||
// HIGH gap after sufficient sync pulses
|
||||
self.step = DecoderStep::GapLow;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
// LOW pulse
|
||||
if duration_diff!(duration, PC_TE_SYNC) < TE_DELTA {
|
||||
self.sync_count += 1;
|
||||
} else if self.sync_count >= PC_SYNC_MIN
|
||||
&& duration_diff!(duration, PC_TE_GAP) < TE_DELTA
|
||||
{
|
||||
// LOW gap after sufficient sync pulses
|
||||
self.step = DecoderStep::GapHigh;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GapHigh: expect the complementary HIGH gap pulse
|
||||
DecoderStep::GapHigh => {
|
||||
if level && duration_diff!(duration, PC_TE_GAP) < TE_DELTA {
|
||||
self.raw_data = 0;
|
||||
self.bit_count = 0;
|
||||
self.step = DecoderStep::Data;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
// GapLow: expect the complementary LOW gap pulse
|
||||
DecoderStep::GapLow => {
|
||||
if !level && duration_diff!(duration, PC_TE_GAP) < TE_DELTA {
|
||||
self.raw_data = 0;
|
||||
self.bit_count = 0;
|
||||
self.step = DecoderStep::Data;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
// Data: decode bit pairs.
|
||||
// LOW pulses are saved in te_last; HIGH pulses complete the bit:
|
||||
// SHORT LOW + LONG HIGH = bit 0
|
||||
// LONG LOW + SHORT HIGH = bit 1
|
||||
DecoderStep::Data => {
|
||||
if level {
|
||||
// HIGH pulse completes a bit pair
|
||||
let bit_value;
|
||||
if duration_diff!(self.te_last, TE_SHORT) < TE_DELTA
|
||||
&& duration_diff!(duration, TE_LONG) < TE_DELTA
|
||||
{
|
||||
bit_value = false; // bit 0
|
||||
} else if duration_diff!(self.te_last, TE_LONG) < TE_DELTA
|
||||
&& duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||
{
|
||||
bit_value = true; // bit 1
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
return None;
|
||||
}
|
||||
|
||||
self.raw_data = (self.raw_data << 1) | (bit_value as u64);
|
||||
self.bit_count += 1;
|
||||
|
||||
if self.bit_count >= MIN_COUNT_BIT {
|
||||
let result = parse_data(self.raw_data);
|
||||
self.step = DecoderStep::Reset;
|
||||
return Some(result);
|
||||
}
|
||||
} else {
|
||||
// LOW pulse: save duration for the bit pair
|
||||
self.te_last = duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PorscheTouaregDecoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
+345
-140
@@ -1,13 +1,14 @@
|
||||
//! PSA (Peugeot/Citroën) protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/psa.c`.
|
||||
//! Decode/encode logic (Manchester, preamble, TEA, XOR, mode 0x23/0x36) matches reference.
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - Manchester encoding: 250/500µs symbol (125/250µs sub-symbol for preamble)
|
||||
//! - 128 bits total: key1 (64) + validation (16) + key2/rest (48); decode uses key1 + 16-bit validation
|
||||
//! - TEA decrypt/encrypt with fixed key schedules; mode 0x23 adds XOR layer
|
||||
//! - Two modes: seed 0x23 (TEA + XOR), seed 0xF3/0x36 (TEA, BF2 key schedule)
|
||||
//! - 128 bits total: key1 (64) + validation (16) + key2/rest (48)
|
||||
//! - Modified TEA (XTEA-like) with dynamic key selection (sum&3, (sum>>11)&3)
|
||||
//! - Mode 0x23: direct XOR decrypt with checksum validation
|
||||
//! - Mode 0x36: TEA brute-force with BF1/BF2 key schedules (deferred)
|
||||
//! - Dual preamble: Pattern 1 (250µs) and Pattern 2 (125µs)
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
@@ -21,7 +22,6 @@ const MIN_COUNT_BIT: usize = 128;
|
||||
// Internal timing for Manchester sub-symbol detection
|
||||
const TE_SHORT_125: u32 = 125;
|
||||
const TE_LONG_250: u32 = 250;
|
||||
const TE_TOLERANCE_49: u32 = 49;
|
||||
const TE_TOLERANCE_50: u32 = 50;
|
||||
const TE_TOLERANCE_99: u32 = 99;
|
||||
const TE_END_1000: u32 = 1000;
|
||||
@@ -45,13 +45,19 @@ enum ManchesterState {
|
||||
Start1,
|
||||
}
|
||||
|
||||
/// Decoder states (matches protopirate's PsaDecoderState)
|
||||
/// Decoder states (matches protopirate's PsaDecoderState 0-4)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderState {
|
||||
/// State0: Wait for first edge (pattern detection)
|
||||
WaitEdge,
|
||||
CountPattern,
|
||||
DecodeManchester,
|
||||
End,
|
||||
/// State1: Count 250µs preamble patterns (Pattern 1)
|
||||
CountPattern250,
|
||||
/// State2: Manchester decode at 250/500µs (Pattern 1)
|
||||
DecodeManchester250,
|
||||
/// State3: Count 125µs preamble patterns (Pattern 2)
|
||||
CountPattern125,
|
||||
/// State4: Manchester decode at 125/250µs (Pattern 2)
|
||||
DecodeManchester125,
|
||||
}
|
||||
|
||||
/// PSA protocol decoder
|
||||
@@ -140,111 +146,268 @@ impl PsaDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
/// TEA decrypt (matches psa.c / standard TEA)
|
||||
/// Modified TEA decrypt with dynamic key selection (matches psa.c psa_tea_decrypt)
|
||||
/// Uses XTEA-like key scheduling: key[sum&3] and key[(sum>>11)&3]
|
||||
fn tea_decrypt(v0: &mut u32, v1: &mut u32, key: &[u32; 4]) {
|
||||
let mut sum = TEA_DELTA.wrapping_mul(TEA_ROUNDS);
|
||||
for _ in 0..TEA_ROUNDS {
|
||||
*v1 = v1.wrapping_sub(
|
||||
(v0.wrapping_shl(4).wrapping_add(key[2]))
|
||||
^ (v0.wrapping_add(sum))
|
||||
^ (v0.wrapping_shr(5).wrapping_add(key[3])),
|
||||
);
|
||||
*v0 = v0.wrapping_sub(
|
||||
(v1.wrapping_shl(4).wrapping_add(key[0]))
|
||||
^ (v1.wrapping_add(sum))
|
||||
^ (v1.wrapping_shr(5).wrapping_add(key[1])),
|
||||
);
|
||||
let k_idx2 = ((sum >> 11) & 3) as usize;
|
||||
let temp = key[k_idx2].wrapping_add(sum);
|
||||
sum = sum.wrapping_sub(TEA_DELTA);
|
||||
*v1 = v1.wrapping_sub(
|
||||
temp ^ (v0.wrapping_shr(5) ^ v0.wrapping_shl(4)).wrapping_add(*v0),
|
||||
);
|
||||
let k_idx1 = (sum & 3) as usize;
|
||||
let temp = key[k_idx1].wrapping_add(sum);
|
||||
*v0 = v0.wrapping_sub(
|
||||
temp ^ (v1.wrapping_shr(5) ^ v1.wrapping_shl(4)).wrapping_add(*v1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// TEA encrypt (matches psa.c / standard TEA)
|
||||
/// Modified TEA encrypt with dynamic key selection (matches psa.c psa_tea_encrypt)
|
||||
fn tea_encrypt(v0: &mut u32, v1: &mut u32, key: &[u32; 4]) {
|
||||
let mut sum: u32 = 0;
|
||||
for _ in 0..TEA_ROUNDS {
|
||||
let k_idx1 = (sum & 3) as usize;
|
||||
let temp = key[k_idx1].wrapping_add(sum);
|
||||
sum = sum.wrapping_add(TEA_DELTA);
|
||||
*v0 = v0.wrapping_add(
|
||||
(v1.wrapping_shl(4).wrapping_add(key[0]))
|
||||
^ (v1.wrapping_add(sum))
|
||||
^ (v1.wrapping_shr(5).wrapping_add(key[1])),
|
||||
temp ^ (v1.wrapping_shr(5) ^ v1.wrapping_shl(4)).wrapping_add(*v1),
|
||||
);
|
||||
let k_idx2 = ((sum >> 11) & 3) as usize;
|
||||
let temp = key[k_idx2].wrapping_add(sum);
|
||||
*v1 = v1.wrapping_add(
|
||||
(v0.wrapping_shl(4).wrapping_add(key[2]))
|
||||
^ (v0.wrapping_add(sum))
|
||||
^ (v0.wrapping_shr(5).wrapping_add(key[3])),
|
||||
temp ^ (v0.wrapping_shr(5) ^ v0.wrapping_shl(4)).wrapping_add(*v0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// XOR decrypt for mode 0x23 (matches psa.c)
|
||||
/// XOR decrypt for mode 0x23 (matches psa.c psa_second_stage_xor_decrypt)
|
||||
/// Uses psa_copy_reverse byte reordering then XOR operations
|
||||
fn xor_decrypt(buffer: &mut [u8]) {
|
||||
// psa_copy_reverse: reorder source bytes
|
||||
let temp = [
|
||||
buffer[5], // temp[0] = source[5]
|
||||
buffer[4], // temp[1] = source[4]
|
||||
buffer[3], // temp[2] = source[3]
|
||||
buffer[2], // temp[3] = source[2]
|
||||
buffer[9], // temp[4] = source[9]
|
||||
buffer[8], // temp[5] = source[8]
|
||||
buffer[7], // temp[6] = source[7]
|
||||
buffer[6], // temp[7] = source[6]
|
||||
];
|
||||
buffer[2] = temp[0] ^ temp[6];
|
||||
buffer[3] = temp[2] ^ temp[0];
|
||||
buffer[4] = temp[6] ^ temp[3];
|
||||
buffer[5] = temp[7] ^ temp[1];
|
||||
buffer[6] = temp[3] ^ temp[1];
|
||||
buffer[7] = temp[6] ^ temp[4] ^ temp[5];
|
||||
}
|
||||
|
||||
/// XOR encrypt for mode 0x23 encoding (matches psa.c psa_second_stage_xor_encrypt)
|
||||
fn xor_encrypt(buffer: &mut [u8]) {
|
||||
let e6 = buffer[8];
|
||||
let e7 = buffer[9];
|
||||
let e5 = buffer[7];
|
||||
let e0 = buffer[2];
|
||||
let e1 = buffer[3];
|
||||
let e2 = buffer[4];
|
||||
let e3 = buffer[5];
|
||||
let e4 = buffer[6];
|
||||
let p0 = buffer[2];
|
||||
let p1 = buffer[3];
|
||||
let p2 = buffer[4];
|
||||
let p3 = buffer[5];
|
||||
let p4 = buffer[6];
|
||||
let p5 = buffer[7];
|
||||
|
||||
buffer[2] = e0 ^ e5;
|
||||
buffer[3] = e1 ^ (e0 ^ e5 ^ e6 ^ e7);
|
||||
buffer[4] = e2 ^ e0;
|
||||
buffer[5] = e3 ^ (e0 ^ e5 ^ e6 ^ e7);
|
||||
buffer[6] = e4 ^ e2;
|
||||
buffer[7] = e5 ^ e6 ^ e7;
|
||||
let ne5 = p5 ^ e7 ^ e6;
|
||||
let ne0 = p2 ^ ne5;
|
||||
let ne2 = p4 ^ ne0;
|
||||
let ne4 = p3 ^ ne2;
|
||||
let ne3 = p0 ^ ne5;
|
||||
let ne1 = p1 ^ ne3;
|
||||
|
||||
buffer[2] = ne0;
|
||||
buffer[3] = ne1;
|
||||
buffer[4] = ne2;
|
||||
buffer[5] = ne3;
|
||||
buffer[6] = ne4;
|
||||
buffer[7] = ne5;
|
||||
}
|
||||
|
||||
/// Decrypt key1 + validation: mode 0x23 (TEA+XOR) or 0x36 (TEA, BF2) — matches psa.c
|
||||
/// Calculate checksum over buffer[2..8] (matches psa.c psa_calculate_checksum)
|
||||
fn calculate_checksum(buffer: &[u8]) -> u8 {
|
||||
let mut checksum: u32 = 0;
|
||||
for i in 2..8 {
|
||||
checksum += (buffer[i] & 0xF) as u32 + ((buffer[i] >> 4) & 0xF) as u32;
|
||||
}
|
||||
((checksum.wrapping_mul(0x10)) & 0xFF) as u8
|
||||
}
|
||||
|
||||
/// Check if direct XOR is allowed by key2 high byte (matches psa.c)
|
||||
fn direct_xor_allowed_by_key2(key2_high_byte: u8) -> bool {
|
||||
let lo = key2_high_byte & 0xF;
|
||||
if lo < 3 { return true; }
|
||||
if lo < 7 && (key2_high_byte & 0xC) != 0 { return true; }
|
||||
false
|
||||
}
|
||||
|
||||
/// Setup byte buffer from key1/key2 (matches psa.c psa_setup_byte_buffer)
|
||||
fn setup_byte_buffer(buffer: &mut [u8], key1_low: u32, key1_high: u32, key2_low: u32) {
|
||||
for i in 0..8usize {
|
||||
let shift = i * 8;
|
||||
let byte_val = if shift < 32 {
|
||||
((key1_low >> shift) & 0xFF) as u8
|
||||
} else {
|
||||
((key1_high >> (shift - 32)) & 0xFF) as u8
|
||||
};
|
||||
buffer[7 - i] = byte_val;
|
||||
}
|
||||
buffer[9] = (key2_low & 0xFF) as u8;
|
||||
buffer[8] = ((key2_low >> 8) & 0xFF) as u8;
|
||||
}
|
||||
|
||||
/// Decrypt using the C code's approach: setup_byte_buffer then attempt direct XOR
|
||||
/// with checksum validation; mode 0x36 is marked for brute-force (matches psa.c)
|
||||
fn try_decrypt(&self) -> Option<(u32, u8, u32, u16, u8)> {
|
||||
// Try mode 0x23 first (seed byte 0x23)
|
||||
let seed_byte = (self.key1_high >> 24) as u8;
|
||||
// C: key2_low = decode_data_low (the 16-bit validation sits in the low word)
|
||||
let key2_low = self.validation_field as u32;
|
||||
|
||||
if seed_byte >= 0x23 && seed_byte < 0x24 {
|
||||
// Mode 0x23 - TEA + XOR
|
||||
let mut v0 = self.key1_high;
|
||||
let mut v1 = self.key1_low;
|
||||
Self::tea_decrypt(&mut v0, &mut v1, &BF1_KEY_SCHEDULE);
|
||||
let mut buffer = [0u8; 48];
|
||||
Self::setup_byte_buffer(&mut buffer, self.key1_low, self.key1_high, key2_low);
|
||||
|
||||
let mut buffer = [0u8; 10];
|
||||
buffer[0] = (v0 >> 24) as u8;
|
||||
buffer[1] = (v0 >> 16) as u8;
|
||||
buffer[2] = (v0 >> 8) as u8;
|
||||
buffer[3] = (v0 >> 0) as u8;
|
||||
buffer[4] = (v1 >> 24) as u8;
|
||||
buffer[5] = (v1 >> 16) as u8;
|
||||
buffer[6] = (v1 >> 8) as u8;
|
||||
buffer[7] = (v1 >> 0) as u8;
|
||||
buffer[8] = (self.validation_field >> 8) as u8;
|
||||
buffer[9] = (self.validation_field & 0xFF) as u8;
|
||||
let key2_high_byte = buffer[8];
|
||||
|
||||
// Try direct XOR decrypt (mode 0x23) if allowed by key2 filter
|
||||
if Self::direct_xor_allowed_by_key2(key2_high_byte) {
|
||||
let checksum = Self::calculate_checksum(&buffer);
|
||||
let validation_result = (checksum ^ key2_high_byte) & 0xF0;
|
||||
|
||||
if validation_result == 0 {
|
||||
// Direct XOR decrypt succeeded validation
|
||||
Self::xor_decrypt(&mut buffer);
|
||||
|
||||
let serial = ((buffer[2] as u32) << 16)
|
||||
| ((buffer[3] as u32) << 8)
|
||||
let serial = ((buffer[3] as u32) << 8)
|
||||
| ((buffer[2] as u32) << 16)
|
||||
| (buffer[4] as u32);
|
||||
let counter = ((buffer[5] as u32) << 8) | (buffer[6] as u32);
|
||||
let counter = (buffer[6] as u32) | ((buffer[5] as u32) << 8);
|
||||
let crc = buffer[7] as u16;
|
||||
let btn = buffer[8] & 0x0F;
|
||||
|
||||
return Some((serial, btn, counter, crc, 0x23));
|
||||
}
|
||||
}
|
||||
|
||||
if seed_byte >= 0xF3 && seed_byte < 0xF4 {
|
||||
// Mode 0x36 - TEA + different key schedule
|
||||
let mut v0 = self.key1_high;
|
||||
let mut v1 = self.key1_low;
|
||||
Self::tea_decrypt(&mut v0, &mut v1, &BF2_KEY_SCHEDULE);
|
||||
// Mode 0x36 - TEA brute-force path
|
||||
// Try direct TEA decrypt with BF1 key schedule for a quick decode attempt
|
||||
{
|
||||
let mut w0 = ((buffer[3] as u32) << 16)
|
||||
| ((buffer[2] as u32) << 24)
|
||||
| ((buffer[4] as u32) << 8)
|
||||
| (buffer[5] as u32);
|
||||
let mut w1 = ((buffer[7] as u32) << 16)
|
||||
| ((buffer[6] as u32) << 24)
|
||||
| ((buffer[8] as u32) << 8)
|
||||
| (buffer[9] as u32);
|
||||
|
||||
let serial = ((v0 >> 8) & 0xFFFF00) | ((v0 & 0xFF) as u32);
|
||||
let counter = v1 >> 16;
|
||||
let btn = ((v1 >> 8) & 0xF) as u8;
|
||||
let crc = (v1 & 0xFF) as u16;
|
||||
Self::tea_decrypt(&mut w0, &mut w1, &BF1_KEY_SCHEDULE);
|
||||
|
||||
// Check if the TEA CRC validates (sum of bytes)
|
||||
let crc_calc = Self::calculate_tea_crc(w0, w1);
|
||||
if crc_calc == (w1 & 0xFF) as u8 {
|
||||
let mut dec_buffer = [0u8; 48];
|
||||
dec_buffer[2] = ((w0 >> 24) & 0xFF) as u8;
|
||||
dec_buffer[3] = ((w0 >> 16) & 0xFF) as u8;
|
||||
dec_buffer[4] = ((w0 >> 8) & 0xFF) as u8;
|
||||
dec_buffer[5] = (w0 & 0xFF) as u8;
|
||||
dec_buffer[6] = ((w1 >> 24) & 0xFF) as u8;
|
||||
dec_buffer[7] = ((w1 >> 16) & 0xFF) as u8;
|
||||
dec_buffer[8] = ((w1 >> 8) & 0xFF) as u8;
|
||||
dec_buffer[9] = (w1 & 0xFF) as u8;
|
||||
|
||||
let btn = (dec_buffer[5] >> 4) & 0xF;
|
||||
let serial = ((dec_buffer[3] as u32) << 8)
|
||||
| ((dec_buffer[2] as u32) << 16)
|
||||
| (dec_buffer[4] as u32);
|
||||
let counter = ((dec_buffer[7] as u32) << 8)
|
||||
| ((dec_buffer[6] as u32) << 16)
|
||||
| (dec_buffer[8] as u32)
|
||||
| (((dec_buffer[5] as u32) & 0xF) << 24);
|
||||
let crc = dec_buffer[9] as u16;
|
||||
|
||||
return Some((serial, btn, counter, crc, 0x36));
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot decrypt - return raw data
|
||||
// Also try BF2 key schedule directly (XOR-derived keys)
|
||||
{
|
||||
let mut w0 = ((buffer[3] as u32) << 16)
|
||||
| ((buffer[2] as u32) << 24)
|
||||
| ((buffer[4] as u32) << 8)
|
||||
| (buffer[5] as u32);
|
||||
let mut w1 = ((buffer[7] as u32) << 16)
|
||||
| ((buffer[6] as u32) << 24)
|
||||
| ((buffer[8] as u32) << 8)
|
||||
| (buffer[9] as u32);
|
||||
|
||||
Self::tea_decrypt(&mut w0, &mut w1, &BF2_KEY_SCHEDULE);
|
||||
|
||||
let crc_calc = Self::calculate_tea_crc(w0, w1);
|
||||
if crc_calc == (w1 & 0xFF) as u8 {
|
||||
let mut dec_buffer = [0u8; 48];
|
||||
dec_buffer[2] = ((w0 >> 24) & 0xFF) as u8;
|
||||
dec_buffer[3] = ((w0 >> 16) & 0xFF) as u8;
|
||||
dec_buffer[4] = ((w0 >> 8) & 0xFF) as u8;
|
||||
dec_buffer[5] = (w0 & 0xFF) as u8;
|
||||
dec_buffer[6] = ((w1 >> 24) & 0xFF) as u8;
|
||||
dec_buffer[7] = ((w1 >> 16) & 0xFF) as u8;
|
||||
dec_buffer[8] = ((w1 >> 8) & 0xFF) as u8;
|
||||
dec_buffer[9] = (w1 & 0xFF) as u8;
|
||||
|
||||
let btn = (dec_buffer[5] >> 4) & 0xF;
|
||||
let serial = ((dec_buffer[3] as u32) << 8)
|
||||
| ((dec_buffer[2] as u32) << 16)
|
||||
| (dec_buffer[4] as u32);
|
||||
let counter = ((dec_buffer[7] as u32) << 8)
|
||||
| ((dec_buffer[6] as u32) << 16)
|
||||
| (dec_buffer[8] as u32)
|
||||
| (((dec_buffer[5] as u32) & 0xF) << 24);
|
||||
let crc = dec_buffer[9] as u16;
|
||||
|
||||
return Some((serial, btn, counter, crc, 0x36));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Calculate TEA CRC (matches psa.c psa_calculate_tea_crc)
|
||||
fn calculate_tea_crc(v0: u32, v1: u32) -> u8 {
|
||||
let mut crc: u32 = 0;
|
||||
crc += (v0 >> 24) & 0xFF;
|
||||
crc += (v0 >> 16) & 0xFF;
|
||||
crc += (v0 >> 8) & 0xFF;
|
||||
crc += v0 & 0xFF;
|
||||
crc += (v1 >> 24) & 0xFF;
|
||||
crc += (v1 >> 16) & 0xFF;
|
||||
crc += (v1 >> 8) & 0xFF;
|
||||
(crc & 0xFF) as u8
|
||||
}
|
||||
|
||||
fn init_preamble_state(&mut self) {
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.pattern_counter = 0;
|
||||
self.bit_count = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
|
||||
fn finalize_frame(&mut self) -> Option<DecodedSignal> {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
if self.bit_count >= 80 {
|
||||
// C validation: ((key1_high >> 16) & 0xF) == 0xA
|
||||
if ((self.key1_high >> 16) & 0xF) != 0xA {
|
||||
return None;
|
||||
}
|
||||
let result = self.parse_data();
|
||||
return Some(result);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -317,24 +480,96 @@ impl ProtocolDecoder for PsaDecoder {
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.state {
|
||||
// State0: detect preamble pattern type
|
||||
DecoderState::WaitEdge => {
|
||||
if level && duration_diff!(duration, TE_SHORT_125) < TE_TOLERANCE_49 {
|
||||
self.state = DecoderState::CountPattern;
|
||||
if !level {
|
||||
return None;
|
||||
}
|
||||
let diff_250 = duration_diff!(duration, TE_SHORT);
|
||||
let diff_125 = duration_diff!(duration, TE_SHORT_125);
|
||||
|
||||
if diff_250 < TE_TOLERANCE_99 {
|
||||
// Pattern 1: 250µs preamble
|
||||
self.init_preamble_state();
|
||||
self.state = DecoderState::CountPattern250;
|
||||
} else if diff_125 < 40 && duration <= 180 {
|
||||
// Pattern 2: 125µs preamble
|
||||
self.init_preamble_state();
|
||||
self.state = DecoderState::CountPattern125;
|
||||
}
|
||||
self.prev_duration = duration;
|
||||
}
|
||||
|
||||
// State1: count 250µs preamble (Pattern 1)
|
||||
DecoderState::CountPattern250 => {
|
||||
if level {
|
||||
return None;
|
||||
}
|
||||
let diff_short = duration_diff!(duration, TE_SHORT);
|
||||
if diff_short < TE_TOLERANCE_99 + 1 {
|
||||
let prev_diff = duration_diff!(self.prev_duration, TE_SHORT);
|
||||
if prev_diff <= TE_TOLERANCE_99 {
|
||||
self.pattern_counter += 1;
|
||||
}
|
||||
self.prev_duration = duration;
|
||||
} else {
|
||||
let diff_long = duration_diff!(duration, TE_LONG);
|
||||
if diff_long < 100 && self.pattern_counter > 0x46 {
|
||||
// Transition to Manchester decode at 250/500µs
|
||||
self.state = DecoderState::DecodeManchester250;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
self.pattern_counter = 0;
|
||||
self.prev_duration = duration;
|
||||
} else {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
self.pattern_counter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DecoderState::CountPattern => {
|
||||
// State2: Manchester decode at 250/500µs (Pattern 1)
|
||||
DecoderState::DecodeManchester250 => {
|
||||
if self.bit_count >= 121 {
|
||||
return self.finalize_frame();
|
||||
}
|
||||
// Check for end-of-frame marker
|
||||
if level && self.bit_count == 80 && duration >= 800 {
|
||||
let end_diff = duration_diff!(duration, TE_END_1000);
|
||||
if end_diff <= 199 {
|
||||
return self.finalize_frame();
|
||||
}
|
||||
}
|
||||
let is_short = duration_diff!(duration, TE_SHORT) < TE_DELTA;
|
||||
let is_long = duration_diff!(duration, TE_LONG) < TE_DELTA;
|
||||
|
||||
if duration > 10000 {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
self.pattern_counter = 0;
|
||||
return None;
|
||||
}
|
||||
|
||||
if is_short || is_long {
|
||||
if let Some(bit) = self.manchester_advance(is_short, level) {
|
||||
self.add_bit(bit);
|
||||
}
|
||||
}
|
||||
self.prev_duration = duration;
|
||||
}
|
||||
|
||||
// State3: count 125µs preamble (Pattern 2)
|
||||
DecoderState::CountPattern125 => {
|
||||
let diff_125 = duration_diff!(duration, TE_SHORT_125);
|
||||
let diff_250 = duration_diff!(duration, TE_LONG_250);
|
||||
|
||||
if diff_125 < TE_TOLERANCE_50 {
|
||||
self.pattern_counter += 1;
|
||||
self.prev_duration = duration;
|
||||
} else if diff_250 < TE_TOLERANCE_99 && self.pattern_counter >= 0x46 {
|
||||
// Found end of preamble, start Manchester decoding
|
||||
self.state = DecoderState::DecodeManchester;
|
||||
} else if diff_250 < TE_TOLERANCE_99 && self.pattern_counter >= 0x45 {
|
||||
// Transition to Manchester decode at 125/250µs
|
||||
self.state = DecoderState::DecodeManchester125;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
@@ -347,23 +582,17 @@ impl ProtocolDecoder for PsaDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
DecoderState::DecodeManchester => {
|
||||
let is_short = duration_diff!(duration, TE_SHORT) < TE_DELTA;
|
||||
let is_long = duration_diff!(duration, TE_LONG) < TE_DELTA;
|
||||
let is_end = duration > TE_END_1000;
|
||||
|
||||
if is_end || self.bit_count >= 121 {
|
||||
// End of data
|
||||
self.state = DecoderState::End;
|
||||
|
||||
if self.bit_count >= 96 {
|
||||
// Got enough data
|
||||
let result = self.parse_data();
|
||||
self.state = DecoderState::WaitEdge;
|
||||
return Some(result);
|
||||
// State4: Manchester decode at 125/250µs (Pattern 2)
|
||||
DecoderState::DecodeManchester125 => {
|
||||
if self.bit_count >= 121 {
|
||||
return self.finalize_frame();
|
||||
}
|
||||
self.state = DecoderState::WaitEdge;
|
||||
return None;
|
||||
let is_short = duration_diff!(duration, TE_SHORT_125) < TE_TOLERANCE_50;
|
||||
let is_long = duration_diff!(duration, TE_LONG_250) < TE_TOLERANCE_99;
|
||||
let is_end = duration > 500;
|
||||
|
||||
if is_end {
|
||||
return self.finalize_frame();
|
||||
}
|
||||
|
||||
if is_short || is_long {
|
||||
@@ -373,13 +602,8 @@ impl ProtocolDecoder for PsaDecoder {
|
||||
} else {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
}
|
||||
|
||||
self.prev_duration = duration;
|
||||
}
|
||||
|
||||
DecoderState::End => {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
@@ -406,31 +630,8 @@ impl ProtocolDecoder for PsaDecoder {
|
||||
buffer[8] = button & 0x0F;
|
||||
buffer[9] = 0;
|
||||
|
||||
// XOR encrypt
|
||||
{
|
||||
let e6 = buffer[8];
|
||||
let e7 = buffer[9];
|
||||
let p0 = buffer[2];
|
||||
let p1 = buffer[3];
|
||||
let p2 = buffer[4];
|
||||
let p3 = buffer[5];
|
||||
let p4 = buffer[6];
|
||||
let p5 = buffer[7];
|
||||
|
||||
let ne5 = p5 ^ e7 ^ e6;
|
||||
let ne0 = p2 ^ ne5;
|
||||
let ne2 = p4 ^ ne0;
|
||||
let ne4 = p3 ^ ne2;
|
||||
let ne3 = p0 ^ ne5;
|
||||
let ne1 = p1 ^ ne3;
|
||||
|
||||
buffer[2] = ne0;
|
||||
buffer[3] = ne1;
|
||||
buffer[4] = ne2;
|
||||
buffer[5] = ne3;
|
||||
buffer[6] = ne4;
|
||||
buffer[7] = ne5;
|
||||
}
|
||||
// XOR encrypt (matches psa.c psa_second_stage_xor_encrypt)
|
||||
Self::xor_encrypt(&mut buffer);
|
||||
|
||||
// TEA encrypt
|
||||
let mut v0 = ((buffer[0] as u32) << 24)
|
||||
@@ -450,38 +651,42 @@ impl ProtocolDecoder for PsaDecoder {
|
||||
|
||||
let mut signal = Vec::with_capacity(512);
|
||||
|
||||
// Preamble + sync (matches protopirate psa encode)
|
||||
for _ in 0..70 {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT_125));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT_125));
|
||||
}
|
||||
// Preamble: 80 iterations at 250us HIGH+LOW (matches C: te = PSA_TE_LONG_250)
|
||||
for _ in 0..80 {
|
||||
signal.push(LevelDuration::new(true, TE_LONG_250));
|
||||
signal.push(LevelDuration::new(false, TE_LONG_250));
|
||||
}
|
||||
|
||||
// Key1: 64 bits Manchester, then validation 16 bits
|
||||
// Sync transition: LOW 250us, HIGH 500us, LOW 250us (matches C)
|
||||
signal.push(LevelDuration::new(false, TE_LONG_250));
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
signal.push(LevelDuration::new(false, TE_LONG_250));
|
||||
|
||||
// Key1: 64 bits Manchester at 250us (C: bit 1 = true,false; bit 0 = false,true)
|
||||
let key1 = ((key1_high as u64) << 32) | (key1_low as u64);
|
||||
for bit in (0..64).rev() {
|
||||
if (key1 >> bit) & 1 == 1 {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_LONG_250));
|
||||
signal.push(LevelDuration::new(false, TE_LONG_250));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_LONG_250));
|
||||
signal.push(LevelDuration::new(true, TE_LONG_250));
|
||||
}
|
||||
}
|
||||
|
||||
// Validation: 16 bits Manchester encoded
|
||||
// Validation: 16 bits Manchester at 250us
|
||||
for bit in (0..16).rev() {
|
||||
if (validation >> bit) & 1 == 1 {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_LONG_250));
|
||||
signal.push(LevelDuration::new(false, TE_LONG_250));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_LONG_250));
|
||||
signal.push(LevelDuration::new(true, TE_LONG_250));
|
||||
}
|
||||
}
|
||||
|
||||
// End marker
|
||||
// End marker: HIGH 1000us + LOW 1000us (matches C)
|
||||
signal.push(LevelDuration::new(true, TE_END_1000));
|
||||
signal.push(LevelDuration::new(false, TE_END_1000));
|
||||
|
||||
Some(signal)
|
||||
|
||||
@@ -0,0 +1,919 @@
|
||||
//! PSA2 (Peugeot/Citroën — "PSA OLD") protocol decoder/encoder
|
||||
//!
|
||||
//! Aligned with Flipper-ARF reference: `lib/subghz/protocols/psa2.c` and `psa2.h`
|
||||
//! (internal name `SUBGHZ_PROTOCOL_PSA2_NAME = "PSA OLD"`). This is the OLDER PSA variant,
|
||||
//! distinct from KAT's existing `psa` (modified-TEA/XEA) decoder.
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - Manchester encoding: 250/500µs symbol (Pattern 1, standard rate) or 125/250µs (Pattern 2,
|
||||
//! half rate). Canonical Flipper `manchester_advance` table (events ShortLow=0, ShortHigh=2,
|
||||
//! LongLow=4, LongHigh=6), seeded `ManchesterStateMid1`.
|
||||
//! - 128-bit frame = key1 (64 bits) + key2/validation word. The decoder collects 64 bits → key1,
|
||||
//! then 16 more (to 80 bits = `KEY2_BITS`) → the 16-bit validation field / key2_low.
|
||||
//! - RF: AM (OOK). Frequency: 433.92 MHz.
|
||||
//! - Crypto: TEA (Tiny Encryption Algorithm) with a dual brute-force fallback (BF1
|
||||
//! 0x23000000–0x24000000, BF2 0xF3000000–0xF4000000) and a mode23/mode36 selector, validated
|
||||
//! via a nibble checksum.
|
||||
//!
|
||||
//! ## Decoder structure & PERFORMANCE
|
||||
//! The C live decoder (`subghz_protocol_decoder_psa2_feed`) only ever runs the cheap mode23 XOR
|
||||
//! path (`psa_decrypt_fast`) per frame — it NEVER runs the TEA brute force. The brute force
|
||||
//! (`psa_decrypt_full`, marked `__attribute__((unused))`) is reserved for the deferred-decrypt
|
||||
//! UI button, not the streaming decoder. KAT mirrors this exactly. Per frame:
|
||||
//!
|
||||
//! 1. Manchester-collect to exactly 80 bits (key1 + validation) with end-of-packet detection.
|
||||
//! 2. Run the O(1) `direct_xor_decrypt` (mode23) gated on its nibble checksum.
|
||||
//! 3. Emit only on a successful, field-bearing decrypt (see `finalize_frame` for why the C's bare
|
||||
//! `(validation & 0xF) == 0xA` emission is not reproduced in KAT's feed-all model).
|
||||
//!
|
||||
//! The bounded TEA brute force (BF1/BF2, ~16.7M iters each) is ported faithfully in
|
||||
//! `Psa2Decoder::decrypt_full` but is NEVER called from `feed()` — only the cheap O(1) gate runs
|
||||
//! per pulse, so the test sweep stays fast.
|
||||
//!
|
||||
//! Decoder steps: State0 (wait preamble) → State1/State3 (count preamble pulses) →
|
||||
//! State2/State4 (Manchester decode + decrypt). Encoder supported (mode23 path).
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
// Standard-rate timings (Pattern 1)
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 100;
|
||||
const MIN_COUNT_BIT: usize = 128;
|
||||
|
||||
// Half-rate timings (Pattern 2 / State 3-4)
|
||||
const TE_SHORT_HALF: u32 = 125;
|
||||
const TE_LONG_HALF: u32 = 250;
|
||||
const TOL_HALF: u32 = 50;
|
||||
|
||||
// End-of-packet markers
|
||||
const TE_END_1000: u32 = 1000;
|
||||
const TE_END_500: u32 = 500;
|
||||
|
||||
// Bit counts
|
||||
const KEY1_BITS: usize = 64; // 0x40
|
||||
const KEY2_BITS: usize = 80; // 0x50
|
||||
const MAX_BITS: usize = 121; // 0x79
|
||||
|
||||
// Preamble pulse-count thresholds (C: PSA_PATTERN_THRESHOLD_1/2)
|
||||
const PATTERN_THRESHOLD_1: u16 = 0x46;
|
||||
const PATTERN_THRESHOLD_2: u16 = 0x45;
|
||||
|
||||
// Validation nibble for the mode23 path: (validation_field & 0xF) == 0xA
|
||||
const VALID_NIBBLE: u16 = 0xA;
|
||||
|
||||
// "decrypted" success marker (C uses 0x50)
|
||||
const DECRYPTED_OK: u16 = 0x50;
|
||||
|
||||
// Mode selectors (stored as ASCII chars in firmware)
|
||||
const MODE_23: u8 = 0x23; // '#'
|
||||
const MODE_36: u8 = 0x36; // '6'
|
||||
|
||||
// PSA2 button codes are Lock=0, Unlock=1, Trunk=2 (psa_button_name). Decodes whose 4-bit button
|
||||
// exceeds this are coincidental matches on unrelated Manchester data and are rejected.
|
||||
const BTN_MAX_VALID: u8 = 0x2;
|
||||
|
||||
// TEA constants
|
||||
const TEA_DELTA: u32 = 0x9E3779B9;
|
||||
const TEA_ROUNDS: u32 = 32;
|
||||
|
||||
// BF1 brute-force range + constants (FUN_08028f94 / FUN_080291c0)
|
||||
const BF1_START: u32 = 0x2300_0000;
|
||||
const BF1_END: u32 = 0x2400_0000;
|
||||
const BF1_CONST_U4: u32 = 0x0E0F_5C41;
|
||||
const BF1_CONST_U5: u32 = 0x0F5C_4123;
|
||||
const BF1_KEY_SCHEDULE: [u32; 4] = [0x4A43_4915, 0xD674_3C2B, 0x1F29_D308, 0xE6B7_9A64];
|
||||
|
||||
// BF2 brute-force range + key schedule (FUN_080290f8)
|
||||
const BF2_START: u32 = 0xF300_0000;
|
||||
const BF2_END: u32 = 0xF400_0000;
|
||||
const BF2_KEY_SCHEDULE: [u32; 4] = [0x4039_C240, 0xEDA9_2CAB, 0x4306_C02A, 0x0219_2A04];
|
||||
|
||||
/// Canonical Flipper Manchester states (lib/toolbox/manchester_decoder.c order:
|
||||
/// Start1=0, Mid1=1, Mid0=2, Start0=3).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Start1 = 0,
|
||||
Mid1 = 1,
|
||||
Mid0 = 2,
|
||||
Start0 = 3,
|
||||
}
|
||||
|
||||
/// Decoder states (matches PSADecoderState0-4 in psa2.c).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderState {
|
||||
/// State0: wait for preamble start.
|
||||
WaitEdge,
|
||||
/// State1: count standard-rate (250µs) preamble pulses.
|
||||
CountPattern250,
|
||||
/// State2: receive key1 + key2/validation at standard rate.
|
||||
DecodeManchester250,
|
||||
/// State3: count half-rate (125µs) preamble pulses.
|
||||
CountPattern125,
|
||||
/// State4: receive key1 + key2/validation at half rate.
|
||||
DecodeManchester125,
|
||||
}
|
||||
|
||||
/// Result of a successful decrypt: (serial, button, counter, crc, type).
|
||||
type DecryptResult = (u32, u8, u32, u16, u8);
|
||||
|
||||
/// PSA2 ("PSA OLD") protocol decoder.
|
||||
pub struct Psa2Decoder {
|
||||
state: DecoderState,
|
||||
prev_duration: u32,
|
||||
manchester_state: ManchesterState,
|
||||
pattern_counter: u16,
|
||||
data_low: u32,
|
||||
data_high: u32,
|
||||
bit_count: usize,
|
||||
// Decoded fields
|
||||
key1_low: u32,
|
||||
key1_high: u32,
|
||||
validation_field: u16,
|
||||
key2_low: u32,
|
||||
key2_high: u32,
|
||||
}
|
||||
|
||||
impl Psa2Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: DecoderState::WaitEdge,
|
||||
prev_duration: 0,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
pattern_counter: 0,
|
||||
data_low: 0,
|
||||
data_high: 0,
|
||||
bit_count: 0,
|
||||
key1_low: 0,
|
||||
key1_high: 0,
|
||||
validation_field: 0,
|
||||
key2_low: 0,
|
||||
key2_high: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn near(dur: u32, target: u32, tol: u32) -> bool {
|
||||
duration_diff!(dur, target) <= tol
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Manchester — canonical Flipper transition table (transitions[] in
|
||||
// manchester_decoder.c). `event` ∈ {0,2,4,6}; returns Some(bit) on emit.
|
||||
// =========================================================================
|
||||
fn manchester_advance(&mut self, event: u8) -> Option<bool> {
|
||||
const TRANSITIONS: [u8; 4] = [0b0000_0001, 0b1001_0001, 0b1001_1011, 0b1111_1011];
|
||||
let state_idx = self.manchester_state as usize;
|
||||
let new_idx = (TRANSITIONS[state_idx] >> event) & 0x3;
|
||||
let new_state = match new_idx {
|
||||
0 => ManchesterState::Start1,
|
||||
1 => ManchesterState::Mid1,
|
||||
2 => ManchesterState::Mid0,
|
||||
_ => ManchesterState::Start0,
|
||||
};
|
||||
|
||||
if new_idx as usize == state_idx {
|
||||
// No progress → reset to Mid1, emit nothing.
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
return None;
|
||||
}
|
||||
self.manchester_state = new_state;
|
||||
match new_state {
|
||||
ManchesterState::Mid0 => Some(false),
|
||||
ManchesterState::Mid1 => Some(true),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn manchester_reset(&mut self) {
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
|
||||
/// Shift one decoded bit into the 64-bit accumulator (data_high:data_low),
|
||||
/// latching key1 at 64 bits (matches psa2.c State2/State4 add path).
|
||||
fn add_bit(&mut self, bit: bool) {
|
||||
let carry = (self.data_low >> 31) & 1;
|
||||
self.data_low = (self.data_low << 1) | (bit as u32);
|
||||
self.data_high = (self.data_high << 1) | carry;
|
||||
self.bit_count += 1;
|
||||
if self.bit_count == KEY1_BITS {
|
||||
self.key1_low = self.data_low;
|
||||
self.key1_high = self.data_high;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn init_preamble_state(&mut self) {
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.pattern_counter = 0;
|
||||
self.bit_count = 0;
|
||||
self.manchester_reset();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// CRYPTO PRIMITIVES (faithful to psa2.c)
|
||||
// =========================================================================
|
||||
|
||||
/// TEA encrypt (FUN_08028e14): dynamic key index `sum&3` then `(sum>>11)&3`.
|
||||
fn tea_encrypt(v0: &mut u32, v1: &mut u32, key: &[u32; 4]) {
|
||||
let (mut a, mut b) = (*v0, *v1);
|
||||
let mut sum: u32 = 0;
|
||||
for _ in 0..TEA_ROUNDS {
|
||||
let t = key[(sum & 3) as usize].wrapping_add(sum);
|
||||
sum = sum.wrapping_add(TEA_DELTA);
|
||||
a = a.wrapping_add(t ^ ((b >> 5) ^ (b << 4)).wrapping_add(b));
|
||||
let t = key[((sum >> 11) & 3) as usize].wrapping_add(sum);
|
||||
b = b.wrapping_add(t ^ ((a >> 5) ^ (a << 4)).wrapping_add(a));
|
||||
}
|
||||
*v0 = a;
|
||||
*v1 = b;
|
||||
}
|
||||
|
||||
/// TEA decrypt (FUN_08028e14 inverse): unwinds the encrypt rounds.
|
||||
fn tea_decrypt(v0: &mut u32, v1: &mut u32, key: &[u32; 4]) {
|
||||
let (mut a, mut b) = (*v0, *v1);
|
||||
let mut sum: u32 = TEA_DELTA.wrapping_mul(TEA_ROUNDS);
|
||||
for _ in 0..TEA_ROUNDS {
|
||||
let t = key[((sum >> 11) & 3) as usize].wrapping_add(sum);
|
||||
sum = sum.wrapping_sub(TEA_DELTA);
|
||||
b = b.wrapping_sub(t ^ ((a >> 5) ^ (a << 4)).wrapping_add(a));
|
||||
let t = key[(sum & 3) as usize].wrapping_add(sum);
|
||||
a = a.wrapping_sub(t ^ ((b >> 5) ^ (b << 4)).wrapping_add(b));
|
||||
}
|
||||
*v0 = a;
|
||||
*v1 = b;
|
||||
}
|
||||
|
||||
/// Byte-sum CRC over 7 bytes of TEA output (FUN_08028e60).
|
||||
fn calculate_tea_crc(v0: u32, v1: u32) -> u8 {
|
||||
let mut crc: u32 = ((v0 >> 24) & 0xFF) + ((v0 >> 16) & 0xFF) + ((v0 >> 8) & 0xFF) + (v0 & 0xFF);
|
||||
crc += ((v1 >> 24) & 0xFF) + ((v1 >> 16) & 0xFF) + ((v1 >> 8) & 0xFF);
|
||||
(crc & 0xFF) as u8
|
||||
}
|
||||
|
||||
/// CRC-16/BUYPASS (poly 0x8005, init 0, no reflection) (FUN_08029098).
|
||||
fn calculate_crc16_bf2(data: &[u8]) -> u16 {
|
||||
let mut crc: u16 = 0;
|
||||
for &byte in data {
|
||||
crc ^= (byte as u16) << 8;
|
||||
for _ in 0..8 {
|
||||
if crc & 0x8000 != 0 {
|
||||
crc = (crc << 1) ^ 0x8005;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
crc
|
||||
}
|
||||
|
||||
/// Fill buf[0..9] from key1/key2 (FUN_080291c0 first loop / psa_setup_byte_buffer).
|
||||
/// key1 big-endian reversed into buf[7..0]; key2_low low/high bytes into buf[9]/buf[8].
|
||||
fn setup_byte_buffer(buf: &mut [u8], key1_low: u32, key1_high: u32, key2_low: u32) {
|
||||
for i in 0..8usize {
|
||||
let shift = i * 8;
|
||||
let b = if shift < 32 {
|
||||
(key1_low >> shift) as u8
|
||||
} else {
|
||||
(key1_high >> (shift - 32)) as u8
|
||||
};
|
||||
buf[7 - i] = b;
|
||||
}
|
||||
buf[9] = (key2_low & 0xFF) as u8;
|
||||
buf[8] = ((key2_low >> 8) & 0xFF) as u8;
|
||||
}
|
||||
|
||||
/// Nibble checksum over buf[2..8] → buf[11] (FUN_08028cf8).
|
||||
fn calculate_checksum(buf: &mut [u8]) {
|
||||
let mut sum: u32 = 0;
|
||||
for &b in buf.iter().take(8).skip(2) {
|
||||
sum += (b & 0xF) as u32 + ((b >> 4) & 0xF) as u32;
|
||||
}
|
||||
buf[11] = (sum.wrapping_mul(0x10) & 0xFF) as u8;
|
||||
}
|
||||
|
||||
/// XOR decrypt second stage (FUN_08028d54 + psa_copy_reverse FUN_08028d24).
|
||||
fn second_stage_xor_decrypt(buf: &mut [u8]) {
|
||||
// psa_copy_reverse
|
||||
let t = [
|
||||
buf[5], buf[4], buf[3], buf[2], buf[9], buf[8], buf[7], buf[6],
|
||||
];
|
||||
buf[2] = t[0] ^ t[6];
|
||||
buf[3] = t[2] ^ t[0];
|
||||
buf[4] = t[6] ^ t[3];
|
||||
buf[5] = t[7] ^ t[1];
|
||||
buf[6] = t[3] ^ t[1];
|
||||
buf[7] = t[6] ^ t[4] ^ t[5];
|
||||
}
|
||||
|
||||
/// Inverse of `second_stage_xor_decrypt`, used by the encoder.
|
||||
fn second_stage_xor_encrypt(buf: &mut [u8]) {
|
||||
let e6 = buf[8];
|
||||
let e7 = buf[9];
|
||||
let (p0, p1, p2, p3, p4, p5) = (buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
|
||||
let e5 = p5 ^ e7 ^ e6;
|
||||
let e0 = p2 ^ e5;
|
||||
let e2 = p4 ^ e0;
|
||||
let e4 = p3 ^ e2;
|
||||
let e3 = p0 ^ e5;
|
||||
let e1 = p1 ^ e3;
|
||||
buf[2] = e0;
|
||||
buf[3] = e1;
|
||||
buf[4] = e2;
|
||||
buf[5] = e3;
|
||||
buf[6] = e4;
|
||||
buf[7] = e5;
|
||||
}
|
||||
|
||||
/// Pack buf[2..9] into two TEA words (FUN_08028f4c).
|
||||
fn prepare_tea_data(buf: &[u8]) -> (u32, u32) {
|
||||
let w0 = ((buf[2] as u32) << 24) | ((buf[3] as u32) << 16) | ((buf[4] as u32) << 8) | buf[5] as u32;
|
||||
let w1 = ((buf[6] as u32) << 24) | ((buf[7] as u32) << 16) | ((buf[8] as u32) << 8) | buf[9] as u32;
|
||||
(w0, w1)
|
||||
}
|
||||
|
||||
/// Unpack two TEA words back into buf[2..9] (FUN_08028e88).
|
||||
fn unpack_tea_result(buf: &mut [u8], v0: u32, v1: u32) {
|
||||
buf[2] = (v0 >> 24) as u8;
|
||||
buf[3] = (v0 >> 16) as u8;
|
||||
buf[4] = (v0 >> 8) as u8;
|
||||
buf[5] = v0 as u8;
|
||||
buf[6] = (v1 >> 24) as u8;
|
||||
buf[7] = (v1 >> 16) as u8;
|
||||
buf[8] = (v1 >> 8) as u8;
|
||||
buf[9] = v1 as u8;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// FIELD EXTRACTION (FUN_08028f10)
|
||||
// =========================================================================
|
||||
|
||||
fn extract_fields_mode23(buf: &[u8]) -> DecryptResult {
|
||||
let button = buf[8] & 0xF;
|
||||
let serial = ((buf[2] as u32) << 16) | ((buf[3] as u32) << 8) | buf[4] as u32;
|
||||
let counter = (buf[6] as u32) | ((buf[5] as u32) << 8);
|
||||
let crc = buf[7] as u16;
|
||||
(serial, button, counter, crc, MODE_23)
|
||||
}
|
||||
|
||||
fn extract_fields_mode36(buf: &[u8]) -> DecryptResult {
|
||||
let button = (buf[5] >> 4) & 0xF;
|
||||
let serial = ((buf[2] as u32) << 16) | ((buf[3] as u32) << 8) | buf[4] as u32;
|
||||
let counter = ((buf[7] as u32) << 8)
|
||||
| ((buf[6] as u32) << 16)
|
||||
| (buf[8] as u32)
|
||||
| (((buf[5] as u32) & 0xF) << 24);
|
||||
let crc = buf[9] as u16;
|
||||
(serial, button, counter, crc, MODE_36)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DECRYPTION PATHS (faithful to psa2.c)
|
||||
// =========================================================================
|
||||
|
||||
/// key2-high gate (matches KAT's sibling `psa::direct_xor_allowed_by_key2`). The PSA2 C
|
||||
/// `psa_direct_xor_decrypt` validates only on the 4-bit checksum nibble `(checksum ^ key2_high)
|
||||
/// & 0xF0 == 0`, which has a ~1/16 false-positive rate on arbitrary 80-bit Manchester data. In
|
||||
/// KAT's "feed every frequency-compatible decoder, first match wins" model that lets PSA2 steal
|
||||
/// unrelated 250/500µs frames (e.g. VAG at 434 MHz). This precondition — the exact filter KAT's
|
||||
/// existing `psa` decoder already applies before its XOR path — is added on top of the C gate to
|
||||
/// suppress those false positives without dropping any genuine PSA2 decode.
|
||||
fn direct_xor_allowed_by_key2(key2_high_byte: u8) -> bool {
|
||||
let lo = key2_high_byte & 0xF;
|
||||
if lo < 3 {
|
||||
return true;
|
||||
}
|
||||
if lo < 7 && (key2_high_byte & 0xC) != 0 {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// mode23 XOR path (FUN_08028d98 / psa_direct_xor_decrypt). O(1) — the only path the
|
||||
/// live decoder runs. Returns the extracted fields on checksum validation.
|
||||
///
|
||||
/// Beyond the C's nibble-checksum gate, this applies two false-positive suppressors required by
|
||||
/// KAT's feed-all-decoders model: the `direct_xor_allowed_by_key2` precondition and a valid-
|
||||
/// button check (PSA2 buttons are Lock=0, Unlock=1, Trunk=2 only — see `psa_button_name`). Both
|
||||
/// cleanly separate genuine PSA2 frames from coincidental matches on unrelated Manchester data.
|
||||
fn direct_xor_decrypt(key1_low: u32, key1_high: u32, key2_low: u32) -> Option<DecryptResult> {
|
||||
let mut buf = [0u8; 48];
|
||||
Self::setup_byte_buffer(&mut buf, key1_low, key1_high, key2_low);
|
||||
|
||||
let key2_high = buf[8];
|
||||
if !Self::direct_xor_allowed_by_key2(key2_high) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Self::calculate_checksum(&mut buf);
|
||||
let checksum = buf[11];
|
||||
let validation = (checksum ^ key2_high) & 0xF0;
|
||||
|
||||
if validation == 0 {
|
||||
// Firmware: update buf[8] high nibble before XOR stage.
|
||||
buf[8] = (buf[8] & 0x0F) | (checksum & 0xF0);
|
||||
buf[13] = buf[9] ^ buf[8];
|
||||
Self::second_stage_xor_decrypt(&mut buf);
|
||||
let fields = Self::extract_fields_mode23(&buf);
|
||||
// Reject implausible button codes (PSA2: Lock=0/Unlock=1/Trunk=2).
|
||||
if fields.1 > BTN_MAX_VALID {
|
||||
return None;
|
||||
}
|
||||
return Some(fields);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// BF1 brute force, range 0x23000000–0x24000000 (FUN_08028f94).
|
||||
///
|
||||
/// PERFORMANCE: up to ~16.7M TEA iterations. NEVER called from `feed()`. Only invoked from
|
||||
/// [`Self::decrypt_full`], which itself runs only after the cheap structural gate. Returns
|
||||
/// `(result, seed)` on success.
|
||||
fn brute_force_decrypt_bf1(key1_low: u32, key1_high: u32, key2_low: u32) -> Option<(DecryptResult, u32)> {
|
||||
let mut buf = [0u8; 48];
|
||||
Self::setup_byte_buffer(&mut buf, key1_low, key1_high, key2_low);
|
||||
let (w0, w1) = Self::prepare_tea_data(&buf);
|
||||
|
||||
for counter in BF1_START..BF1_END {
|
||||
// Derive the working key with two TEA encrypts.
|
||||
let (mut wk2, mut wk3) = (BF1_CONST_U4, counter);
|
||||
Self::tea_encrypt(&mut wk2, &mut wk3, &BF1_KEY_SCHEDULE);
|
||||
let (mut wk0, mut wk1) = ((counter << 8) | 0x0E, BF1_CONST_U5);
|
||||
Self::tea_encrypt(&mut wk0, &mut wk1, &BF1_KEY_SCHEDULE);
|
||||
let wkey = [wk0, wk1, wk2, wk3];
|
||||
|
||||
let (mut dv0, mut dv1) = (w0, w1);
|
||||
Self::tea_decrypt(&mut dv0, &mut dv1, &wkey);
|
||||
|
||||
if (counter & 0xFFFFFF) == (dv0 >> 8) {
|
||||
let crc = Self::calculate_tea_crc(dv0, dv1);
|
||||
if crc == (dv1 & 0xFF) as u8 {
|
||||
let mut out = [0u8; 48];
|
||||
Self::unpack_tea_result(&mut out, dv0, dv1);
|
||||
return Some((Self::extract_fields_mode36(&out), counter));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// BF2 brute force, range 0xF3000000–0xF4000000 (FUN_080290f8).
|
||||
///
|
||||
/// PERFORMANCE: up to ~16.7M TEA iterations. NEVER called from `feed()` (see BF1 note).
|
||||
fn brute_force_decrypt_bf2(key1_low: u32, key1_high: u32, key2_low: u32) -> Option<(DecryptResult, u32)> {
|
||||
let mut buf = [0u8; 48];
|
||||
Self::setup_byte_buffer(&mut buf, key1_low, key1_high, key2_low);
|
||||
let (w0, w1) = Self::prepare_tea_data(&buf);
|
||||
|
||||
for counter in BF2_START..BF2_END {
|
||||
let wkey = [
|
||||
BF2_KEY_SCHEDULE[0] ^ counter,
|
||||
BF2_KEY_SCHEDULE[1] ^ counter,
|
||||
BF2_KEY_SCHEDULE[2] ^ counter,
|
||||
BF2_KEY_SCHEDULE[3] ^ counter,
|
||||
];
|
||||
let (mut dv0, mut dv1) = (w0, w1);
|
||||
Self::tea_decrypt(&mut dv0, &mut dv1, &wkey);
|
||||
|
||||
if (counter & 0xFFFFFF) == (dv0 >> 8) {
|
||||
let crc_buf = [
|
||||
(dv0 >> 24) as u8,
|
||||
(dv0 >> 16) as u8,
|
||||
(dv0 >> 8) as u8,
|
||||
dv0 as u8,
|
||||
(dv1 >> 24) as u8,
|
||||
(dv1 >> 16) as u8,
|
||||
];
|
||||
let crc16 = Self::calculate_crc16_bf2(&crc_buf);
|
||||
let expected = ((dv1 & 0xFF) | (((dv1 >> 16) & 0xFF) << 8)) as u16;
|
||||
if crc16 == expected {
|
||||
let mut out = [0u8; 48];
|
||||
Self::unpack_tea_result(&mut out, dv0, dv1);
|
||||
return Some((Self::extract_fields_mode36(&out), counter));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Full decrypt router (FUN_080291c0, the `__attribute__((unused))` `psa_decrypt_full`):
|
||||
/// try XOR (mode23), then BF1, then BF2.
|
||||
///
|
||||
/// PERFORMANCE: this can run the bounded brute force (~33M TEA iterations worst case). It is
|
||||
/// NOT part of the live `feed()` path — `feed()` uses only `direct_xor_decrypt`. This mirrors
|
||||
/// the C, where `psa_decrypt_full` is unused by the decoder and the feed callback calls
|
||||
/// `psa_decrypt_fast` (XOR only). Exposed for completeness / offline decrypt; gate any caller
|
||||
/// behind the structural frame check so the brute force never runs on arbitrary data.
|
||||
#[allow(dead_code)]
|
||||
fn decrypt_full(key1_low: u32, key1_high: u32, key2_low: u32) -> Option<DecryptResult> {
|
||||
if let Some(r) = Self::direct_xor_decrypt(key1_low, key1_high, key2_low) {
|
||||
return Some(r);
|
||||
}
|
||||
if let Some((r, _seed)) = Self::brute_force_decrypt_bf1(key1_low, key1_high, key2_low) {
|
||||
return Some(r);
|
||||
}
|
||||
if let Some((r, _seed)) = Self::brute_force_decrypt_bf2(key1_low, key1_high, key2_low) {
|
||||
return Some(r);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Build the encoded key material for a mode23 frame (psa_build_encrypt_mode23 /
|
||||
/// FUN_08029028). Returns `(key1_high, key1_low, validation_field)`.
|
||||
fn encode_mode23(serial: u32, button: u8, counter: u16) -> (u32, u32, u16) {
|
||||
let mut buf = [0u8; 48];
|
||||
buf[2] = (serial >> 16) as u8;
|
||||
buf[3] = (serial >> 8) as u8;
|
||||
buf[4] = serial as u8;
|
||||
buf[5] = (counter >> 8) as u8;
|
||||
buf[6] = counter as u8;
|
||||
buf[7] = 0; // CRC placeholder
|
||||
buf[8] = button & 0xF;
|
||||
buf[9] = 0; // key2_low low byte
|
||||
Self::second_stage_xor_encrypt(&mut buf);
|
||||
Self::calculate_checksum(&mut buf);
|
||||
buf[8] = (buf[8] & 0x0F) | (buf[11] & 0xF0);
|
||||
buf[13] = buf[9] ^ buf[8];
|
||||
// buf[0]/buf[1] preamble bytes (no original key material → derive from data).
|
||||
buf[0] = buf[2] ^ buf[6];
|
||||
buf[1] = buf[3] ^ buf[7];
|
||||
|
||||
let key1_high =
|
||||
((buf[0] as u32) << 24) | ((buf[1] as u32) << 16) | ((buf[2] as u32) << 8) | buf[3] as u32;
|
||||
let key1_low =
|
||||
((buf[4] as u32) << 24) | ((buf[5] as u32) << 16) | ((buf[6] as u32) << 8) | buf[7] as u32;
|
||||
let validation = ((buf[8] as u16) << 8) | buf[9] as u16;
|
||||
(key1_high, key1_low, validation)
|
||||
}
|
||||
|
||||
/// Map KAT's generic button command to a PSA2 button code (Lock=0, Unlock=1, Trunk=2).
|
||||
fn map_button(button: u8) -> u8 {
|
||||
match button {
|
||||
0x01 => 0x0, // Lock
|
||||
0x02 => 0x1, // Unlock
|
||||
0x04 => 0x2, // Trunk
|
||||
0x08 => 0x2, // Panic → Trunk (PSA2 has no panic code)
|
||||
b => b & 0x0F,
|
||||
}
|
||||
}
|
||||
|
||||
/// Finalize a collected 80-bit frame: latch validation/key2, run the cheap mode23 XOR gate,
|
||||
/// emit only on successful decryption. NEVER runs the brute force.
|
||||
///
|
||||
/// The C feed (`subghz_protocol_decoder_psa2_feed`) also emits on a bare
|
||||
/// `(validation_field & 0xF) == 0xA` even when the XOR decrypt fails — but that path yields a
|
||||
/// raw, *undecrypted* frame (no serial/button/counter) that the firmware UI keeps so the user
|
||||
/// can later run the brute-force button. KAT's pipeline feeds every frequency-compatible
|
||||
/// decoder and reports the first that fires, so a 1/16-probability nibble match with no fields
|
||||
/// is indistinguishable from noise and steals unrelated 250/500µs frames (e.g. VAG at 434 MHz).
|
||||
/// We therefore gate emission strictly on a successful, field-bearing decrypt — the same path
|
||||
/// that produces the genuine GROUPE PSA decodes — which is the meaningful half of the C gate.
|
||||
fn finalize_frame(&mut self) -> Option<DecodedSignal> {
|
||||
// C: validation_field = decode_data_low & 0xFFFF; key2_low = decode_data_low.
|
||||
self.validation_field = (self.data_low & 0xFFFF) as u16;
|
||||
self.key2_low = self.data_low;
|
||||
self.key2_high = self.data_high;
|
||||
|
||||
// C key2_low for decrypt is the 16-bit validation word in the low position.
|
||||
let decrypt =
|
||||
Self::direct_xor_decrypt(self.key1_low, self.key1_high, self.validation_field as u32);
|
||||
|
||||
// Reset collection regardless (the C feed always rewinds to State0 after a frame attempt).
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.state = DecoderState::WaitEdge;
|
||||
|
||||
let (serial, button, counter, _crc, _type) = decrypt?;
|
||||
let _ = (DECRYPTED_OK, VALID_NIBBLE); // C markers documented; emission gated on decrypt.
|
||||
let data = ((self.key1_high as u64) << 32) | self.key1_low as u64;
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter as u16),
|
||||
crc_valid: true,
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for Psa2Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"PSA2"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: TE_SHORT,
|
||||
te_long: TE_LONG,
|
||||
te_delta: TE_DELTA,
|
||||
min_count_bit: MIN_COUNT_BIT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
self.prev_duration = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
self.pattern_counter = 0;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.key1_low = 0;
|
||||
self.key1_high = 0;
|
||||
self.validation_field = 0;
|
||||
self.key2_low = 0;
|
||||
self.key2_high = 0;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.state {
|
||||
// State0: detect preamble pattern type.
|
||||
DecoderState::WaitEdge => {
|
||||
if !level {
|
||||
return None;
|
||||
}
|
||||
self.init_preamble_state();
|
||||
self.prev_duration = duration;
|
||||
if Self::near(duration, TE_SHORT, TE_DELTA) {
|
||||
self.state = DecoderState::CountPattern250;
|
||||
} else if Self::near(duration, TE_SHORT_HALF, TOL_HALF) {
|
||||
self.state = DecoderState::CountPattern125;
|
||||
}
|
||||
}
|
||||
|
||||
// State1: count standard-rate (250µs) preamble pulses.
|
||||
DecoderState::CountPattern250 => {
|
||||
if level {
|
||||
return None;
|
||||
}
|
||||
if Self::near(duration, TE_SHORT, TE_DELTA) {
|
||||
if Self::near(self.prev_duration, TE_SHORT, TE_DELTA) {
|
||||
self.pattern_counter += 1;
|
||||
}
|
||||
self.prev_duration = duration;
|
||||
return None;
|
||||
}
|
||||
if Self::near(duration, TE_LONG, TE_DELTA) {
|
||||
if self.pattern_counter > PATTERN_THRESHOLD_1 {
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.manchester_reset();
|
||||
self.state = DecoderState::DecodeManchester250;
|
||||
}
|
||||
self.pattern_counter = 0;
|
||||
self.prev_duration = duration;
|
||||
return None;
|
||||
}
|
||||
self.state = DecoderState::WaitEdge;
|
||||
self.pattern_counter = 0;
|
||||
}
|
||||
|
||||
// State2: receive key1 + key2/validation at standard rate.
|
||||
DecoderState::DecodeManchester250 => {
|
||||
if self.bit_count >= MAX_BITS {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
return None;
|
||||
}
|
||||
// End-of-packet detection at KEY2_BITS.
|
||||
if level && self.bit_count == KEY2_BITS && Self::near(duration, TE_END_1000, 199) {
|
||||
return self.finalize_frame();
|
||||
}
|
||||
|
||||
let event: Option<u8>;
|
||||
if Self::near(duration, TE_SHORT, TE_DELTA) {
|
||||
event = Some(((level as u8 ^ 1) & 0x7F) << 1);
|
||||
} else if Self::near(duration, TE_LONG, TE_DELTA) {
|
||||
event = Some(if level { 4 } else { 6 });
|
||||
} else {
|
||||
// Out-of-range low pulse: secondary end marker when 80 bits collected. The C
|
||||
// also gates this on a (stale) nibble==0xA; emission is now decided in
|
||||
// finalize_frame (decrypt-or-reject), so we trigger on the end geometry alone.
|
||||
if !level && Self::near(duration, TE_END_1000, 199) && self.bit_count == KEY2_BITS
|
||||
{
|
||||
return self.finalize_frame();
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(ev) = event {
|
||||
if self.bit_count < KEY2_BITS {
|
||||
if let Some(bit) = self.manchester_advance(ev) {
|
||||
self.add_bit(bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.prev_duration = duration;
|
||||
}
|
||||
|
||||
// State3: count half-rate (125µs) preamble pulses.
|
||||
DecoderState::CountPattern125 => {
|
||||
if level {
|
||||
return None;
|
||||
}
|
||||
if Self::near(duration, TE_SHORT_HALF, TOL_HALF) {
|
||||
if Self::near(self.prev_duration, TE_SHORT_HALF, TOL_HALF) {
|
||||
self.pattern_counter += 1;
|
||||
} else {
|
||||
self.pattern_counter = 0;
|
||||
}
|
||||
self.prev_duration = duration;
|
||||
return None;
|
||||
}
|
||||
if (TE_LONG_HALF..0x12C).contains(&duration) {
|
||||
if self.pattern_counter > PATTERN_THRESHOLD_2 {
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.manchester_reset();
|
||||
self.state = DecoderState::DecodeManchester125;
|
||||
}
|
||||
self.pattern_counter = 0;
|
||||
self.prev_duration = duration;
|
||||
return None;
|
||||
}
|
||||
self.state = DecoderState::WaitEdge;
|
||||
}
|
||||
|
||||
// State4: receive key1 + key2/validation at half rate.
|
||||
DecoderState::DecodeManchester125 => {
|
||||
if self.bit_count >= MAX_BITS {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
return None;
|
||||
}
|
||||
if !level {
|
||||
let event: Option<u8> = if Self::near(duration, TE_SHORT_HALF, TOL_HALF) {
|
||||
Some(((level as u8 ^ 1) & 0x7F) << 1)
|
||||
} else if (TE_LONG_HALF..0x12C).contains(&duration) {
|
||||
Some(if level { 4 } else { 6 })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(ev) = event {
|
||||
if let Some(bit) = self.manchester_advance(ev) {
|
||||
self.add_bit(bit);
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
// Rising edge: end-of-packet at 500µs.
|
||||
if Self::near(duration, TE_END_500, 99) {
|
||||
if self.bit_count != KEY2_BITS {
|
||||
return None;
|
||||
}
|
||||
return self.finalize_frame();
|
||||
}
|
||||
}
|
||||
self.prev_duration = duration;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let serial = decoded.serial?;
|
||||
// Increment counter on TX (matches FUN_080299a0 rolling-counter increment).
|
||||
let counter = decoded.counter.unwrap_or(0).wrapping_add(1);
|
||||
|
||||
let (key1_high, key1_low, validation) =
|
||||
Self::encode_mode23(serial, Self::map_button(button), counter);
|
||||
|
||||
// mode23 timings: te=250µs, sync long=500µs, end=1000µs.
|
||||
let te = TE_SHORT;
|
||||
let te_long_sync = TE_LONG;
|
||||
let end_dur = TE_END_1000;
|
||||
|
||||
let mut signal = Vec::with_capacity(600);
|
||||
|
||||
// Preamble: 80 pairs of (HIGH te)+(LOW te).
|
||||
for _ in 0..80 {
|
||||
signal.push(LevelDuration::new(true, te));
|
||||
signal.push(LevelDuration::new(false, te));
|
||||
}
|
||||
|
||||
// Sync: (LOW te) + (HIGH te_long) + (LOW te).
|
||||
signal.push(LevelDuration::new(false, te));
|
||||
signal.push(LevelDuration::new(true, te_long_sync));
|
||||
signal.push(LevelDuration::new(false, te));
|
||||
|
||||
// key1: 64 bits MSB-first. bit=1 → (HIGH,LOW); bit=0 → (LOW,HIGH).
|
||||
let k1 = ((key1_high as u64) << 32) | key1_low as u64;
|
||||
for bit in (0..64).rev() {
|
||||
let b = (k1 >> bit) & 1 == 1;
|
||||
signal.push(LevelDuration::new(b, te));
|
||||
signal.push(LevelDuration::new(!b, te));
|
||||
}
|
||||
|
||||
// validation_field: 16 bits MSB-first.
|
||||
for bit in (0..16).rev() {
|
||||
let b = (validation >> bit) & 1 == 1;
|
||||
signal.push(LevelDuration::new(b, te));
|
||||
signal.push(LevelDuration::new(!b, te));
|
||||
}
|
||||
|
||||
// End burst: (HIGH end) + (LOW end).
|
||||
signal.push(LevelDuration::new(true, end_dur));
|
||||
signal.push(LevelDuration::new(false, end_dur));
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Psa2Decoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tea_round_trip() {
|
||||
// TEA encrypt then decrypt must be the identity for any key.
|
||||
let key = BF1_KEY_SCHEDULE;
|
||||
let (mut v0, mut v1) = (0x1234_5678u32, 0x9ABC_DEF0u32);
|
||||
let (o0, o1) = (v0, v1);
|
||||
Psa2Decoder::tea_encrypt(&mut v0, &mut v1, &key);
|
||||
Psa2Decoder::tea_decrypt(&mut v0, &mut v1, &key);
|
||||
assert_eq!((v0, v1), (o0, o1), "TEA encrypt/decrypt not invertible");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode23_encode_decode_round_trip() {
|
||||
// Encode (serial,button,counter) → key1/validation, then run the mode23 XOR decrypt and
|
||||
// confirm the fields (and a passing nibble checksum) come back exactly. Validates TEA-free
|
||||
// mode23 path: second_stage_xor + nibble checksum + field packing.
|
||||
for &(serial, btn, cnt) in &[
|
||||
(0x99EB25u32, 0u8, 0x039Bu16),
|
||||
(0x123456, 2, 0x0042),
|
||||
(0xABCDEF, 1, 0x1234),
|
||||
(0x0000FF, 0, 0x0001),
|
||||
] {
|
||||
let (k1h, k1l, vf) = Psa2Decoder::encode_mode23(serial, btn, cnt);
|
||||
let decrypt = Psa2Decoder::direct_xor_decrypt(k1l, k1h, vf as u32);
|
||||
assert!(
|
||||
decrypt.is_some(),
|
||||
"mode23 XOR decrypt failed to validate for serial={serial:06X} btn={btn} cnt={cnt:04X}"
|
||||
);
|
||||
let (ds, db, dc, _crc, ty) = decrypt.unwrap();
|
||||
assert_eq!(ds, serial, "serial mismatch");
|
||||
assert_eq!(db, btn, "button mismatch");
|
||||
assert_eq!(dc, cnt as u32, "counter mismatch");
|
||||
assert_eq!(ty, MODE_23, "mode mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checksum_matches_reference() {
|
||||
// Spot-check the nibble checksum against the hand-computed C formula.
|
||||
let mut buf = [0u8; 48];
|
||||
for (i, b) in buf.iter_mut().enumerate().take(8).skip(2) {
|
||||
*b = (i as u8) * 0x11; // 0x22,0x33,0x44,0x55,0x66,0x77
|
||||
}
|
||||
Psa2Decoder::calculate_checksum(&mut buf);
|
||||
// bytes 0x22,0x33,0x44,0x55,0x66,0x77 → nibble sum = (2+2)+(3+3)+(4+4)+(5+5)+(6+6)+(7+7) = 54
|
||||
let expected = ((54u32 * 0x10) & 0xFF) as u8; // 54*16 = 864 = 0x360 → 0x60
|
||||
assert_eq!(buf[11], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_emits_manchester_frame() {
|
||||
// The encoder produces a non-trivial 250/500µs Manchester upload.
|
||||
let dec = Psa2Decoder::new();
|
||||
let decoded = DecodedSignal {
|
||||
serial: Some(0x99EB25),
|
||||
button: Some(0x01),
|
||||
counter: Some(0x039A),
|
||||
crc_valid: true,
|
||||
data: 0,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
};
|
||||
let sig = dec.encode(&decoded, 0x01).expect("encode should succeed");
|
||||
// 80 preamble pairs (160) + 3 sync + 64*2 + 16*2 + 2 end = 160+3+128+32+2 = 325.
|
||||
assert_eq!(sig.len(), 325, "unexpected upload length");
|
||||
assert!(sig.iter().all(|p| p.duration_us > 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
//! Toyota / Lexus KeeLoq protocol decoder (dual variant)
|
||||
//!
|
||||
//! Ported from Flipper-ARF reference: `lib/subghz/protocols/toyota.c` and `toyota.h`.
|
||||
//! Decode-only — the reference `encoder` field is NULL.
|
||||
//!
|
||||
//! Two variants, detected from the first HIGH pulse width (threshold 310µs):
|
||||
//!
|
||||
//! - **Variant A** — Corolla / 433.92 MHz. PWM pairs: te_short=400µs, te_long=800µs,
|
||||
//! delta=175µs. LS (long HIGH + short LOW) = bit 0, SL (short HIGH + long LOW) = bit 1.
|
||||
//! Preamble = repeated short-short (SS) pairs; first non-SS pair is the first data bit.
|
||||
//! Frame = 68 bits; min_count_bit = 60.
|
||||
//! - **Variant B** — Tundra / 315 MHz. NRZ: each individual pulse encodes one bit by a
|
||||
//! midpoint classifier (`<= 287µs` -> 0, `> 287µs` -> 1). Preamble = short-HIGH /
|
||||
//! long-LOW pairs (te_short=200µs, te_long=390µs, delta=120µs) terminated by a sync gap
|
||||
//! (LOW between 1500 and 2600µs). Frame = 67 bits; min_count_bit = 60.
|
||||
//!
|
||||
//! KeeLoq hopping: the hop field is left encrypted (the reference does not decrypt or run a
|
||||
//! CRC). `data` layout matches the reference `generic.data`:
|
||||
//! `(hop << 32) | (serial << 4) | button`, with hop=32 bits, serial=28 bits, button=4 bits.
|
||||
//!
|
||||
//! Emission is gated tightly (exact frame bit count + structural preamble/sync + non-zero
|
||||
//! serial) so it does not false-match the other KeeLoq-PWM protocols (Kia V3/V4, Subaru,
|
||||
//! Suzuki, etc.). The shared 433 MHz KeeLoq-PWM air encoding means a Toyota Variant-A frame
|
||||
//! that also satisfies Kia V3/V4's 68-bit/CRC4 structure is claimed by Kia V3/V4 first (it is
|
||||
//! earlier in the registry); Toyota uniquely claims 60-bit frames and Variant-B NRZ at 315 MHz.
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
// Variant A physical constants (Corolla / 433 MHz)
|
||||
const A_TE_SHORT: u32 = 400;
|
||||
const A_TE_LONG: u32 = 800;
|
||||
const A_TE_DELTA: u32 = 175;
|
||||
|
||||
// Variant B physical constants (Tundra / 315 MHz, preamble classification only)
|
||||
const B_TE_SHORT: u32 = 200;
|
||||
const B_TE_LONG: u32 = 390;
|
||||
const B_TE_DELTA: u32 = 120;
|
||||
|
||||
const MIN_COUNT_BIT: usize = 60;
|
||||
|
||||
/// NRZ midpoint for Variant B data pulses (`<= 287` -> 0, `> 287` -> 1).
|
||||
const B_NRZ_MIDPOINT: u32 = 287;
|
||||
|
||||
/// Sync gap (LOW) separating preamble from data in Variant B.
|
||||
const B_SYNC_GAP_MIN: u32 = 1500;
|
||||
const B_SYNC_GAP_MAX: u32 = 2600;
|
||||
|
||||
/// Minimum preamble pairs before a frame is accepted.
|
||||
const A_PREAMBLE_MIN: u16 = 6;
|
||||
const B_PREAMBLE_MIN: u16 = 6;
|
||||
|
||||
/// Frame lengths in bits.
|
||||
const A_BITS: usize = 68;
|
||||
const B_BITS: usize = 67;
|
||||
|
||||
/// First HIGH duration below this -> Variant B, at or above -> Variant A.
|
||||
const VARIANT_THRESH: u32 = 310;
|
||||
|
||||
#[inline]
|
||||
fn a_is_short(d: u32) -> bool {
|
||||
duration_diff!(d, A_TE_SHORT) < A_TE_DELTA
|
||||
}
|
||||
#[inline]
|
||||
fn a_is_long(d: u32) -> bool {
|
||||
duration_diff!(d, A_TE_LONG) < A_TE_DELTA
|
||||
}
|
||||
#[inline]
|
||||
fn b_is_short(d: u32) -> bool {
|
||||
duration_diff!(d, B_TE_SHORT) < B_TE_DELTA
|
||||
}
|
||||
#[inline]
|
||||
fn b_is_long(d: u32) -> bool {
|
||||
duration_diff!(d, B_TE_LONG) < B_TE_DELTA
|
||||
}
|
||||
|
||||
/// Decoder steps (matches ToyotaDecoderStep in toyota.c).
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
PreambleA,
|
||||
DataA,
|
||||
PreambleB,
|
||||
DataB,
|
||||
}
|
||||
|
||||
/// Toyota / Lexus protocol decoder (matches SubGhzProtocolDecoderToyota).
|
||||
pub struct ToyotaDecoder {
|
||||
step: DecoderStep,
|
||||
/// 128-bit shift accumulator (hi:lo), matching the C `bits_hi`/`bits_lo`.
|
||||
bits_hi: u64,
|
||||
bits_lo: u64,
|
||||
bit_count: usize,
|
||||
te_last: u32,
|
||||
have_high: bool,
|
||||
preamble_count: u16,
|
||||
/// 0 = Variant A (Corolla / 433 MHz), 1 = Variant B (Tundra / 315 MHz).
|
||||
variant: u8,
|
||||
}
|
||||
|
||||
impl ToyotaDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
bits_hi: 0,
|
||||
bits_lo: 0,
|
||||
bit_count: 0,
|
||||
te_last: 0,
|
||||
have_high: false,
|
||||
preamble_count: 0,
|
||||
variant: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the parse state. Matches `subghz_protocol_decoder_toyota_reset`, which intentionally
|
||||
/// does NOT clear `variant` (detected once per session); the variant is re-detected on the next
|
||||
/// pulse from the Reset step anyway.
|
||||
fn reset_state(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.bits_hi = 0;
|
||||
self.bits_lo = 0;
|
||||
self.bit_count = 0;
|
||||
self.te_last = 0;
|
||||
self.have_high = false;
|
||||
self.preamble_count = 0;
|
||||
}
|
||||
|
||||
/// Push one bit into the 128-bit accumulator (matches `toyota_push_bit`).
|
||||
fn push_bit(&mut self, bit: u8) {
|
||||
let carry = (self.bits_lo >> 63) & 1;
|
||||
self.bits_hi = (self.bits_hi << 1) | carry;
|
||||
self.bits_lo = (self.bits_lo << 1) | (bit as u64 & 1);
|
||||
self.bit_count += 1;
|
||||
}
|
||||
|
||||
/// Extract `length` bits at `offset` from the end of the accumulator (matches `toyota_extract`).
|
||||
fn extract(&self, offset: usize, length: usize) -> u32 {
|
||||
let mut result: u32 = 0;
|
||||
let total = self.bit_count as isize;
|
||||
for i in 0..length {
|
||||
let pos = (total - 1) - (offset as isize + i as isize);
|
||||
let b = if pos >= 64 {
|
||||
((self.bits_hi >> (pos - 64)) & 1) as u32
|
||||
} else if pos >= 0 {
|
||||
((self.bits_lo >> pos) & 1) as u32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
result = (result << 1) | b;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Build the decoded signal once a full frame is collected (matches `toyota_decode_and_fire`).
|
||||
/// Returns `None` if the structural gate (bit count / serial) fails.
|
||||
fn decode_and_fire(&self) -> Option<DecodedSignal> {
|
||||
if self.bit_count < MIN_COUNT_BIT {
|
||||
return None;
|
||||
}
|
||||
|
||||
let hop = self.extract(0, 32);
|
||||
let serial = self.extract(32, 28);
|
||||
let button = self.extract(60, 4) as u8;
|
||||
|
||||
// Tight gate: require a non-zero serial so a run of all-zero/garbage bits that happens to
|
||||
// reach the bit count cannot emit a Toyota frame.
|
||||
if serial == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// generic.data = (hop << 32) | (serial << 4) | button
|
||||
let data = ((hop as u64) << 32) | ((serial as u64) << 4) | (button as u64 & 0x0F);
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
// KeeLoq hop is left encrypted (no key / no decrypt in the reference).
|
||||
counter: None,
|
||||
// No CRC in the reference; a fully-structured frame of the exact bit count is the
|
||||
// validity criterion (the callback only fires at min_count_bit).
|
||||
crc_valid: true,
|
||||
data,
|
||||
data_count_bit: self.bit_count,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
protocol_display_name: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Feed for Variant A (Corolla / 433 MHz) — PWM pair encoding (matches `toyota_feed_variant_a`).
|
||||
///
|
||||
/// Faithful to the reference's gap-terminated emission path. The reference ALSO self-fires the
|
||||
/// instant `bit_count` reaches 68; that is intentionally dropped here. Variant A is the same
|
||||
/// KeeLoq-PWM air protocol as Kia V3/V4, and the early self-fire (on the normal short LOW that
|
||||
/// completes bit 68) lands several pulses BEFORE Kia V3/V4's sync-terminated fire — which, in
|
||||
/// KAT's "first decoder to fire on a pulse wins" stream, would let Toyota steal every shared
|
||||
/// 68-bit frame from Kia. By emitting only on the terminating gap (the same pulse Kia fires on),
|
||||
/// the registry order (Kia earlier) resolves the shared frames in Kia's favour, while Toyota
|
||||
/// still uniquely claims 60–67-bit Variant-A frames that Kia rejects. Bit accumulation is capped
|
||||
/// at 68 so the field layout is preserved regardless of trailing repeats.
|
||||
fn feed_variant_a(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::PreambleA => {
|
||||
if level {
|
||||
self.te_last = duration;
|
||||
self.have_high = true;
|
||||
return None;
|
||||
}
|
||||
|
||||
if !self.have_high {
|
||||
self.reset_state();
|
||||
return None;
|
||||
}
|
||||
self.have_high = false;
|
||||
|
||||
let hs = a_is_short(self.te_last);
|
||||
let hl = a_is_long(self.te_last);
|
||||
let ls = a_is_short(duration);
|
||||
let ll = a_is_long(duration);
|
||||
|
||||
if hs && ls {
|
||||
self.preamble_count += 1;
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.preamble_count < A_PREAMBLE_MIN {
|
||||
self.reset_state();
|
||||
return None;
|
||||
}
|
||||
|
||||
self.bits_hi = 0;
|
||||
self.bits_lo = 0;
|
||||
self.bit_count = 0;
|
||||
|
||||
if hl && ls {
|
||||
self.push_bit(0);
|
||||
} else if hs && ll {
|
||||
self.push_bit(1);
|
||||
}
|
||||
|
||||
self.step = DecoderStep::DataA;
|
||||
None
|
||||
}
|
||||
|
||||
DecoderStep::DataA => {
|
||||
if level {
|
||||
if a_is_short(duration) || a_is_long(duration) {
|
||||
self.te_last = duration;
|
||||
self.have_high = true;
|
||||
} else {
|
||||
// Terminating gap / out-of-range HIGH: emit (deferring to Kia on shared
|
||||
// 68-bit frames via registry order — see fn doc-comment).
|
||||
let result = if self.bit_count >= MIN_COUNT_BIT {
|
||||
self.decode_and_fire()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.reset_state();
|
||||
return result;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if !self.have_high {
|
||||
return None;
|
||||
}
|
||||
self.have_high = false;
|
||||
|
||||
// Cap accumulation at A_BITS: once a full 68-bit frame is collected, stop pushing
|
||||
// more bits and wait for the terminating gap. This keeps the field layout fixed and
|
||||
// makes emission coincide with Kia V3/V4's sync-terminated fire so Kia wins shared
|
||||
// frames by registry order.
|
||||
if self.bit_count >= A_BITS {
|
||||
return None;
|
||||
}
|
||||
|
||||
let hs = a_is_short(self.te_last);
|
||||
let hl = a_is_long(self.te_last);
|
||||
let ls = a_is_short(duration);
|
||||
let ll = a_is_long(duration);
|
||||
|
||||
if hl && ls {
|
||||
self.push_bit(0);
|
||||
} else if hs && ll {
|
||||
self.push_bit(1);
|
||||
} else {
|
||||
let result = if self.bit_count >= MIN_COUNT_BIT {
|
||||
self.decode_and_fire()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.reset_state();
|
||||
return result;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Feed for Variant B (Tundra / 315 MHz) — NRZ encoding (matches `toyota_feed_variant_b`).
|
||||
fn feed_variant_b(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::PreambleB => {
|
||||
if level {
|
||||
if b_is_short(duration) {
|
||||
self.te_last = duration;
|
||||
self.have_high = true;
|
||||
} else {
|
||||
self.reset_state();
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
// Falling edge
|
||||
if !self.have_high {
|
||||
self.reset_state();
|
||||
return None;
|
||||
}
|
||||
self.have_high = false;
|
||||
|
||||
// Sync gap: LOW ~1938µs -> transition to data
|
||||
if duration >= B_SYNC_GAP_MIN && duration <= B_SYNC_GAP_MAX {
|
||||
if self.preamble_count >= B_PREAMBLE_MIN {
|
||||
self.bits_hi = 0;
|
||||
self.bits_lo = 0;
|
||||
self.bit_count = 0;
|
||||
self.have_high = false;
|
||||
self.step = DecoderStep::DataB;
|
||||
} else {
|
||||
self.reset_state();
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
// Normal preamble LOW must be LONG
|
||||
if b_is_long(duration) {
|
||||
self.preamble_count += 1;
|
||||
return None;
|
||||
}
|
||||
|
||||
self.reset_state();
|
||||
None
|
||||
}
|
||||
|
||||
DecoderStep::DataB => {
|
||||
// Every pulse (HIGH or LOW) encodes one bit. A pulse >= sync-gap min ends the frame.
|
||||
if duration >= B_SYNC_GAP_MIN {
|
||||
let result = if self.bit_count >= MIN_COUNT_BIT {
|
||||
self.decode_and_fire()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.reset_state();
|
||||
return result;
|
||||
}
|
||||
|
||||
let bit = if duration > B_NRZ_MIDPOINT { 1 } else { 0 };
|
||||
self.push_bit(bit);
|
||||
|
||||
if self.bit_count >= B_BITS {
|
||||
let result = self.decode_and_fire();
|
||||
self.reset_state();
|
||||
return result;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for ToyotaDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Toyota"
|
||||
}
|
||||
|
||||
fn timing(&self) -> ProtocolTiming {
|
||||
ProtocolTiming {
|
||||
te_short: A_TE_SHORT,
|
||||
te_long: A_TE_LONG,
|
||||
te_delta: A_TE_DELTA,
|
||||
min_count_bit: MIN_COUNT_BIT,
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_frequencies(&self) -> &[u32] {
|
||||
// Variant A 433.92 MHz, Variant B 315 MHz.
|
||||
&[433_920_000, 315_000_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.reset_state();
|
||||
// Full reset between segments: also clear the detected variant.
|
||||
self.variant = 0;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
if self.step == DecoderStep::Reset {
|
||||
if !level {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Variant detection from the first SHORT HIGH pulse:
|
||||
// < 310µs -> Variant B (Tundra 315 MHz, te_short ~200µs)
|
||||
// >= 310µs -> Variant A (Corolla 433 MHz, te_short ~400µs)
|
||||
let fits_b = b_is_short(duration) && duration < VARIANT_THRESH;
|
||||
let fits_a = a_is_short(duration) && duration >= VARIANT_THRESH;
|
||||
|
||||
if fits_b {
|
||||
self.variant = 1;
|
||||
self.te_last = duration;
|
||||
self.have_high = true;
|
||||
self.preamble_count = 0;
|
||||
self.step = DecoderStep::PreambleB;
|
||||
} else if fits_a {
|
||||
self.variant = 0;
|
||||
self.te_last = duration;
|
||||
self.have_high = true;
|
||||
self.preamble_count = 0;
|
||||
self.step = DecoderStep::PreambleA;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.variant == 1 {
|
||||
self.feed_variant_b(level, duration)
|
||||
} else {
|
||||
self.feed_variant_a(level, duration)
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ToyotaDecoder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Build a Variant A (PWM) frame: preamble SS pairs, then `bits` MSB-first.
|
||||
/// LS (long HIGH + short LOW) = 0, SL (short HIGH + long LOW) = 1. Terminated by a long gap.
|
||||
fn build_variant_a(bits: &[u8]) -> Vec<LevelDuration> {
|
||||
let mut v = Vec::new();
|
||||
// 8 short-short preamble pairs (>= A_PREAMBLE_MIN = 6).
|
||||
for _ in 0..8 {
|
||||
v.push(LevelDuration::new(true, A_TE_SHORT));
|
||||
v.push(LevelDuration::new(false, A_TE_SHORT));
|
||||
}
|
||||
for &b in bits {
|
||||
if b == 0 {
|
||||
v.push(LevelDuration::new(true, A_TE_LONG));
|
||||
v.push(LevelDuration::new(false, A_TE_SHORT));
|
||||
} else {
|
||||
v.push(LevelDuration::new(true, A_TE_SHORT));
|
||||
v.push(LevelDuration::new(false, A_TE_LONG));
|
||||
}
|
||||
}
|
||||
// Trailing HIGH gap (out of TE range) flushes the frame.
|
||||
v.push(LevelDuration::new(true, 5000));
|
||||
v
|
||||
}
|
||||
|
||||
/// Build a Variant B (NRZ) frame: short-HIGH/long-LOW preamble pairs, sync gap, then each bit
|
||||
/// as one pulse (short = 0, long = 1), terminated by a sync-gap-length pulse.
|
||||
fn build_variant_b(bits: &[u8]) -> Vec<LevelDuration> {
|
||||
let mut v = Vec::new();
|
||||
// 8 preamble pairs: short HIGH (~200µs) + long LOW (~390µs).
|
||||
for _ in 0..8 {
|
||||
v.push(LevelDuration::new(true, B_TE_SHORT));
|
||||
v.push(LevelDuration::new(false, B_TE_LONG));
|
||||
}
|
||||
// Last preamble HIGH, then the sync-gap LOW.
|
||||
v.push(LevelDuration::new(true, B_TE_SHORT));
|
||||
v.push(LevelDuration::new(false, 1938));
|
||||
// Data: each bit one pulse, alternating level (polarity is ignored by the NRZ classifier).
|
||||
for (i, &b) in bits.iter().enumerate() {
|
||||
let level = i % 2 == 0;
|
||||
let dur = if b == 1 { 380 } else { 200 };
|
||||
v.push(LevelDuration::new(level, dur));
|
||||
}
|
||||
// End-of-frame gap.
|
||||
v.push(LevelDuration::new(false, 2000));
|
||||
v
|
||||
}
|
||||
|
||||
fn feed_all(dec: &mut ToyotaDecoder, pairs: &[LevelDuration]) -> Option<DecodedSignal> {
|
||||
for p in pairs {
|
||||
if let Some(sig) = dec.feed(p.level, p.duration_us) {
|
||||
return Some(sig);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variant_a_synthetic_decodes() {
|
||||
// 68-bit frame: a recognizable pattern with a non-zero serial and a known button nibble.
|
||||
// bits[0..32] = hop, bits[32..60] = serial, bits[60..64] = button, bits[64..68] = padding.
|
||||
let mut bits = vec![0u8; A_BITS];
|
||||
// hop = 0x9ABCDEF0 (MSB first in bits[0..32])
|
||||
let hop: u32 = 0x9ABC_DEF0;
|
||||
for i in 0..32 {
|
||||
bits[i] = ((hop >> (31 - i)) & 1) as u8;
|
||||
}
|
||||
// serial = 0x0123456 (28 bits) in bits[32..60]
|
||||
let serial: u32 = 0x012_3456;
|
||||
for i in 0..28 {
|
||||
bits[32 + i] = ((serial >> (27 - i)) & 1) as u8;
|
||||
}
|
||||
// button = 0x8 (Lock) in bits[60..64]
|
||||
let button: u8 = 0x8;
|
||||
for i in 0..4 {
|
||||
bits[60 + i] = (button >> (3 - i)) & 1;
|
||||
}
|
||||
// bits[64..68] padding = 0
|
||||
|
||||
let frame = build_variant_a(&bits);
|
||||
let mut dec = ToyotaDecoder::new();
|
||||
let sig = feed_all(&mut dec, &frame).expect("variant A frame should decode");
|
||||
|
||||
assert_eq!(sig.data_count_bit, A_BITS);
|
||||
assert_eq!(sig.serial, Some(serial));
|
||||
assert_eq!(sig.button, Some(button));
|
||||
// hop is the top 32 bits of data
|
||||
assert_eq!((sig.data >> 32) as u32, hop);
|
||||
assert!(sig.crc_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variant_b_synthetic_decodes() {
|
||||
// 67-bit NRZ frame.
|
||||
let mut bits = vec![0u8; B_BITS];
|
||||
let hop: u32 = 0x1357_9BDF;
|
||||
for i in 0..32 {
|
||||
bits[i] = ((hop >> (31 - i)) & 1) as u8;
|
||||
}
|
||||
let serial: u32 = 0x0AB_CDEF;
|
||||
for i in 0..28 {
|
||||
bits[32 + i] = ((serial >> (27 - i)) & 1) as u8;
|
||||
}
|
||||
// button (bits[60..64]); only 3 bits of button fit before the 67-bit end (bits[60..63]),
|
||||
// bit 63..67 truncated — extract(60,4) reads bits[63..67] from the END. Just assert serial.
|
||||
for i in 0..7 {
|
||||
bits[60 + i] = if i % 2 == 0 { 1 } else { 0 };
|
||||
}
|
||||
|
||||
let frame = build_variant_b(&bits);
|
||||
let mut dec = ToyotaDecoder::new();
|
||||
let sig = feed_all(&mut dec, &frame).expect("variant B frame should decode");
|
||||
|
||||
assert_eq!(sig.data_count_bit, B_BITS);
|
||||
assert_eq!(sig.serial, Some(serial));
|
||||
assert!(sig.crc_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_all_zero_serial() {
|
||||
// A structurally valid 68-bit frame with an all-zero serial must NOT emit (tight gate).
|
||||
let bits = vec![0u8; A_BITS];
|
||||
let frame = build_variant_a(&bits);
|
||||
let mut dec = ToyotaDecoder::new();
|
||||
assert!(feed_all(&mut dec, &frame).is_none());
|
||||
}
|
||||
}
|
||||
+17
-9
@@ -18,6 +18,7 @@ use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use super::aut64;
|
||||
use super::keys;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use tracing;
|
||||
|
||||
// Type 3/4 timing (used as default for ProtocolTiming)
|
||||
const TE_SHORT: u32 = 500;
|
||||
@@ -30,7 +31,8 @@ const MIN_COUNT_BIT: usize = 80;
|
||||
// Type 1/2 timing
|
||||
const TE_SHORT_12: u32 = 300;
|
||||
const TE_LONG_12: u32 = 600;
|
||||
const TE_DELTA_12: u32 = 80; // Preamble1/Data1 (ref vag.c 79/80)
|
||||
#[allow(dead_code)]
|
||||
const TE_DELTA_12: u32 = 80; // Preamble1/Data1 (ref vag.c 79/80); preamble now uses REF_PREAMBLE1_TOL
|
||||
|
||||
// Reference-aligned deltas (vag.c VAG_NEAR / VAG_TOL_300 79, VAG_TOL_500 120)
|
||||
const REF_RESET_DELTA: u32 = 79; // Reset: 300±79, 500±79 for Preamble2
|
||||
@@ -38,6 +40,8 @@ const REF_PREAMBLE_SYNC: u32 = 80; // Preamble2 counting: 500±80
|
||||
const REF_SYNC2_AB_DELTA: u32 = 79; // Sync2A/Sync2B: 500/1000/750±79 (ref VAG_NEAR(..., 79))
|
||||
const REF_SYNC2C_DELTA: u32 = 79; // Sync2C: 750±79
|
||||
const REF_GAP1_DELTA: u32 = 79; // Preamble1→Data1 gap 600µs ±79 (ref check_gap1)
|
||||
// Real-world Type 1/2: preamble often ~280–380µs; ref uses 79/80
|
||||
const REF_PREAMBLE1_TOL: u32 = 100; // 300±100 for Type 1/2 preamble lock/count
|
||||
|
||||
// TEA constants
|
||||
const TEA_DELTA: u32 = 0x9E3779B9;
|
||||
@@ -842,9 +846,9 @@ impl ProtocolDecoder for VagDecoder {
|
||||
if !level {
|
||||
return None;
|
||||
}
|
||||
// Matches vag.c: duration < 300 and (300-duration)<=79 -> Preamble1; else (duration-300)<=79 -> Preamble1; else (duration-300)>79 and 500±79 -> Preamble2
|
||||
// Matches vag.c: duration < 300 and (300-duration)<=tol -> Preamble1; else (duration-300)<=tol -> Preamble1; else 500±79 -> Preamble2. Use REF_PREAMBLE1_TOL for Type 1/2 lock.
|
||||
if duration < TE_SHORT_12 {
|
||||
if (TE_SHORT_12 - duration) > REF_RESET_DELTA {
|
||||
if (TE_SHORT_12 - duration) > REF_PREAMBLE1_TOL {
|
||||
return None;
|
||||
}
|
||||
// init_pattern1
|
||||
@@ -857,8 +861,8 @@ impl ProtocolDecoder for VagDecoder {
|
||||
self.vag_type = VagType::Unknown;
|
||||
self.te_last = duration;
|
||||
self.manchester_advance(ManchesterEvent::Reset);
|
||||
} else if duration.wrapping_sub(TE_SHORT_12) <= REF_RESET_DELTA {
|
||||
// Fall-through to init_pattern1 in ref (duration 300..380)
|
||||
} else if duration.wrapping_sub(TE_SHORT_12) <= REF_PREAMBLE1_TOL {
|
||||
// Fall-through to init_pattern1 in ref (duration 300..300+tol)
|
||||
self.step = DecoderStep::Preamble1;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
@@ -900,14 +904,14 @@ impl ProtocolDecoder for VagDecoder {
|
||||
TE_SHORT_12 - duration
|
||||
};
|
||||
|
||||
// Reference: (300-duration)<=79 or (duration-300)<80 -> count pair (check_preamble1_prev)
|
||||
if te_diff < TE_DELTA_12 {
|
||||
// Reference: (300-duration) or (duration-300) within tol -> count pair. Use REF_PREAMBLE1_TOL for real-world jitter.
|
||||
if te_diff <= REF_PREAMBLE1_TOL {
|
||||
let prev_diff = if self.te_last > TE_SHORT_12 {
|
||||
self.te_last - TE_SHORT_12
|
||||
} else {
|
||||
TE_SHORT_12 - self.te_last
|
||||
};
|
||||
if prev_diff <= REF_RESET_DELTA {
|
||||
if prev_diff <= REF_PREAMBLE1_TOL {
|
||||
self.te_last = duration;
|
||||
self.header_count += 1;
|
||||
return None;
|
||||
@@ -930,7 +934,7 @@ impl ProtocolDecoder for VagDecoder {
|
||||
} else {
|
||||
TE_SHORT_12 - self.te_last
|
||||
};
|
||||
if prev_diff <= REF_RESET_DELTA {
|
||||
if prev_diff <= REF_PREAMBLE1_TOL {
|
||||
self.step = DecoderStep::Data1;
|
||||
return None;
|
||||
}
|
||||
@@ -1005,6 +1009,10 @@ impl ProtocolDecoder for VagDecoder {
|
||||
self.data_count_bit = 80;
|
||||
|
||||
self.parse_data();
|
||||
tracing::debug!(
|
||||
"VAG Data1 decode: 80 bits, decrypted={} (report regardless of key)",
|
||||
self.decrypted
|
||||
);
|
||||
|
||||
let result = self.build_decoded_signal();
|
||||
self.data_low = 0;
|
||||
|
||||
@@ -113,7 +113,7 @@ impl Demodulator {
|
||||
pairs: Vec::with_capacity(2048),
|
||||
total_samples: 0,
|
||||
min_duration_us: 40, // 40µs debounce (was 50 — slightly more permissive)
|
||||
max_gap_us: 20_000, // 20ms gap = end of signal (was 10ms — wider to avoid splitting signals with internal gaps)
|
||||
max_gap_us: 80_000, // 80ms gap = end of signal; keeps multi-burst keyfob presses (e.g. 3–4 bursts with 25–50ms gaps) as one capture
|
||||
samples_since_edge: 0,
|
||||
}
|
||||
}
|
||||
@@ -253,11 +253,11 @@ impl Demodulator {
|
||||
.push(LevelDuration::new(self.current_level, duration_us));
|
||||
}
|
||||
|
||||
// Return the pairs and reset
|
||||
// Return the pairs and reset (min 5 pairs so short/unknown keyfob bursts still show)
|
||||
let result = std::mem::take(&mut self.pairs);
|
||||
self.reset_state();
|
||||
|
||||
if result.len() >= 10 {
|
||||
if result.len() >= 5 {
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
@@ -405,7 +405,7 @@ impl FmDemodulator {
|
||||
pending_count: 0,
|
||||
pairs: Vec::with_capacity(2048),
|
||||
min_duration_us: 40,
|
||||
max_gap_us: 20_000,
|
||||
max_gap_us: 80_000, // match AM: 80ms so one button press (multi-burst) stays one capture
|
||||
samples_since_edge: 0,
|
||||
}
|
||||
}
|
||||
@@ -495,7 +495,7 @@ impl FmDemodulator {
|
||||
}
|
||||
let result = std::mem::take(&mut self.pairs);
|
||||
self.fm_reset_state();
|
||||
if result.len() >= 10 {
|
||||
if result.len() >= 5 {
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
@@ -559,8 +559,8 @@ mod tests {
|
||||
// Process (won't return signal yet since no long gap)
|
||||
let _ = demod.process_samples(&buf);
|
||||
|
||||
// Add a long gap to flush
|
||||
let gap_buf: Vec<i8> = vec![1, 0].repeat(50_000); // 25ms LOW
|
||||
// Add a long gap to flush (>= max_gap_us: 80ms at 2MHz = 160k samples)
|
||||
let gap_buf: Vec<i8> = vec![1, 0].repeat(80_000); // 80ms LOW
|
||||
if let Some(pairs) = demod.process_samples(&gap_buf) {
|
||||
// Verify no consecutive same-level pairs
|
||||
for window in pairs.windows(2) {
|
||||
|
||||
+6
-22
@@ -1,15 +1,13 @@
|
||||
//! Storage management for configuration, exports, and keystores.
|
||||
//! Storage management for configuration and exports.
|
||||
//!
|
||||
//! All application data lives under `~/.config/KAT/`:
|
||||
//! All application data lives under `~/.config/KAT/`. **No keystore directory is created**
|
||||
//! — keys are embedded in the binary (see [crate::protocols::keys] and [crate::keystore]).
|
||||
//!
|
||||
//! ```text
|
||||
//! ~/.config/KAT/
|
||||
//! config.ini — User configuration
|
||||
//! exports/ — Exported .fob / .sub files (save location)
|
||||
//! import/ — Scanned at startup for .fob / .sub to import
|
||||
//! keystore/ — Protocol encryption keys
|
||||
//! keystore.ini — Key definitions (hex values)
|
||||
//! vag.bin — VAG AUT64 binary key file (optional)
|
||||
//! ```
|
||||
//!
|
||||
//! Captures are **in-memory only** and are discarded when KAT exits.
|
||||
@@ -60,7 +58,7 @@ impl Config {
|
||||
export_directory: config_dir.join("exports"),
|
||||
import_directory: config_dir.join("import"),
|
||||
max_captures: 100,
|
||||
research_mode: false,
|
||||
research_mode: true, // show unknown signals by default (researchers need to see them)
|
||||
default_frequency: 433_920_000,
|
||||
default_lna_gain: 24,
|
||||
default_vga_gain: 20,
|
||||
@@ -163,6 +161,7 @@ impl Config {
|
||||
; Location: {path}
|
||||
;
|
||||
; Edit this file to change default settings.
|
||||
; Keys are embedded in the program — no keystore directory is used or created.
|
||||
; Lines starting with ; or # are comments.
|
||||
|
||||
[general]
|
||||
@@ -345,20 +344,10 @@ impl Storage {
|
||||
);
|
||||
}
|
||||
|
||||
// ── 6. Ensure keystore directory exists ────────────────────────
|
||||
let keystore_dir = config_dir.join("keystore");
|
||||
if !keystore_dir.exists() {
|
||||
fs::create_dir_all(&keystore_dir).with_context(|| {
|
||||
format!("Failed to create keystore dir: {:?}", keystore_dir)
|
||||
})?;
|
||||
tracing::info!("Created keystore directory: {:?}", keystore_dir);
|
||||
}
|
||||
|
||||
// ── 7. Log resolved paths ───────────────────────────────────────
|
||||
// ── 6. Log resolved paths ───────────────────────────────────────
|
||||
tracing::info!("Config dir: {:?}", config_dir);
|
||||
tracing::info!("Export dir: {:?}", config.export_directory);
|
||||
tracing::info!("Import dir: {:?}", config.import_directory);
|
||||
tracing::info!("Keystore dir: {:?}", keystore_dir);
|
||||
|
||||
Ok(Self {
|
||||
config_dir,
|
||||
@@ -393,9 +382,4 @@ impl Storage {
|
||||
&self.config.import_directory
|
||||
}
|
||||
|
||||
/// Get the keystore directory path (`~/.config/KAT/keystore`). Kept for optional file-based override.
|
||||
#[allow(dead_code)]
|
||||
pub fn keystore_dir(&self) -> PathBuf {
|
||||
self.config_dir.join("keystore")
|
||||
}
|
||||
}
|
||||
|
||||
+38
-2
@@ -25,7 +25,7 @@ pub fn render_captures_list(frame: &mut Frame, area: Rect, app: &App) {
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Min(6), // Table (flexible, takes remaining)
|
||||
Constraint::Length(12), // Detail panel (fixed height)
|
||||
Constraint::Length(18), // Detail panel (signal + vulnerability; taller for multiple CVEs)
|
||||
])
|
||||
.split(area)
|
||||
} else {
|
||||
@@ -271,6 +271,23 @@ fn render_signal_detail(frame: &mut Frame, area: Rect, capture: &crate::capture:
|
||||
Span::styled(raw_info, raw_style),
|
||||
]));
|
||||
|
||||
// Row 8: File path (imported .sub/.fob only; blank for live captures)
|
||||
let file_display = capture
|
||||
.source_file
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
left_lines.push(Line::from(vec![
|
||||
Span::styled(" File: ", label_style),
|
||||
Span::styled(
|
||||
file_display,
|
||||
if file_display.is_empty() {
|
||||
Style::default().fg(Color::DarkGray)
|
||||
} else {
|
||||
value_style
|
||||
},
|
||||
),
|
||||
]));
|
||||
|
||||
// Build the title
|
||||
let title = format!(
|
||||
" Signal #{:02} — {} ",
|
||||
@@ -314,8 +331,22 @@ fn render_vulnerability_panel(
|
||||
capture.model.as_deref(),
|
||||
capture.region.as_deref(),
|
||||
);
|
||||
let vuln_found = capture.status == CaptureStatus::EncoderCapable || !vulns.is_empty();
|
||||
|
||||
let mut lines = Vec::new();
|
||||
|
||||
// When we can encode, the encryption is broken — complete emulation is available.
|
||||
if capture.status == CaptureStatus::EncoderCapable {
|
||||
let emu_style = Style::default()
|
||||
.fg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
lines.push(Line::from(Span::styled(
|
||||
" Encryption is broken — Complete emulation of the keyfob is available",
|
||||
emu_style,
|
||||
)));
|
||||
lines.push(Line::from(Span::raw("")));
|
||||
}
|
||||
|
||||
if vulns.is_empty() {
|
||||
let has_meta = capture.year.is_some()
|
||||
|| capture.make.is_some()
|
||||
@@ -346,13 +377,18 @@ fn render_vulnerability_panel(
|
||||
Span::styled(" Description: ", label_style),
|
||||
Span::styled(v.description, value_style),
|
||||
]));
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled(" Source: ", label_style),
|
||||
Span::styled(v.url, value_style),
|
||||
]));
|
||||
lines.push(Line::from(Span::raw("")));
|
||||
}
|
||||
}
|
||||
|
||||
let border_color = if vuln_found { Color::Green } else { Color::Yellow };
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Color::Yellow))
|
||||
.border_style(Style::default().fg(border_color))
|
||||
.title(" Vulnerability ");
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
|
||||
+8
-1
@@ -53,6 +53,7 @@ pub fn render_command_line(frame: &mut Frame, area: Rect, app: &App) {
|
||||
| InputMode::FobMetaMake
|
||||
| InputMode::FobMetaModel
|
||||
| InputMode::FobMetaRegion
|
||||
| InputMode::FobMetaCommand
|
||||
| InputMode::FobMetaNotes => (
|
||||
String::new(),
|
||||
"EXPORT",
|
||||
@@ -61,11 +62,17 @@ pub fn render_command_line(frame: &mut Frame, area: Rect, app: &App) {
|
||||
InputMode::CaptureMetaYear
|
||||
| InputMode::CaptureMetaMake
|
||||
| InputMode::CaptureMetaModel
|
||||
| InputMode::CaptureMetaRegion => (
|
||||
| InputMode::CaptureMetaRegion
|
||||
| InputMode::CaptureMetaCommand => (
|
||||
String::new(),
|
||||
"META",
|
||||
Style::default().fg(Color::Cyan),
|
||||
),
|
||||
InputMode::LoadFileBrowser => (
|
||||
String::new(),
|
||||
"LOAD",
|
||||
Style::default().fg(Color::Cyan),
|
||||
),
|
||||
InputMode::License => (
|
||||
String::new(),
|
||||
"LICENSE",
|
||||
|
||||
+118
-18
@@ -4,7 +4,7 @@ use ratatui::{
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, Borders, Clear, Paragraph, Wrap},
|
||||
widgets::{Block, Borders, Clear, List, ListItem, Paragraph, Wrap},
|
||||
Frame,
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ pub fn draw_ui(frame: &mut Frame, app: &App) {
|
||||
let main_area = frame.area();
|
||||
let mut v_constraints = vec![
|
||||
Constraint::Length(3), // Header (full width)
|
||||
Constraint::Min(8), // Middle: captures + RX bar
|
||||
Constraint::Min(26), // Middle: captures table + detail panel (signal + vulnerability)
|
||||
Constraint::Length(3), // Status bar (full width)
|
||||
Constraint::Length(1), // Help bar (full width)
|
||||
];
|
||||
@@ -105,6 +105,7 @@ pub fn draw_ui(frame: &mut Frame, app: &App) {
|
||||
| InputMode::FobMetaMake
|
||||
| InputMode::FobMetaModel
|
||||
| InputMode::FobMetaRegion
|
||||
| InputMode::FobMetaCommand
|
||||
| InputMode::FobMetaNotes
|
||||
) {
|
||||
render_export_form(frame, app);
|
||||
@@ -116,10 +117,15 @@ pub fn draw_ui(frame: &mut Frame, app: &App) {
|
||||
| InputMode::CaptureMetaMake
|
||||
| InputMode::CaptureMetaModel
|
||||
| InputMode::CaptureMetaRegion
|
||||
| InputMode::CaptureMetaCommand
|
||||
) {
|
||||
render_capture_meta_form(frame, app);
|
||||
}
|
||||
|
||||
if app.input_mode == InputMode::LoadFileBrowser {
|
||||
render_load_file_browser(frame, app);
|
||||
}
|
||||
|
||||
if app.input_mode == InputMode::License {
|
||||
render_text_overlay(frame, app, "License", LICENSE_TEXT, Alignment::Left);
|
||||
}
|
||||
@@ -134,11 +140,19 @@ pub fn draw_ui(frame: &mut Frame, app: &App) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the RSSI bar on the right (vertical bar, bottom = strong).
|
||||
/// Render the RSSI bar on the right (vertical bar, bottom = strong). Shows " TX " in red when transmitting.
|
||||
fn render_rssi_bar(frame: &mut Frame, area: Rect, app: &App) {
|
||||
let is_tx = app.radio_state == RadioState::Transmitting;
|
||||
let (title, filled_style, empty_style) = if is_tx {
|
||||
(" TX ", Style::default().fg(Color::Red), Style::default().fg(Color::DarkGray))
|
||||
} else {
|
||||
(" RX ", Style::default().fg(Color::Green), Style::default().fg(Color::DarkGray))
|
||||
};
|
||||
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" RX ");
|
||||
.border_style(if is_tx { Style::default().fg(Color::Red) } else { Style::default() })
|
||||
.title(title);
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
|
||||
@@ -146,23 +160,24 @@ fn render_rssi_bar(frame: &mut Frame, area: Rect, app: &App) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize RSSI (0..~1) to fill ratio; scale so ~0.5 magnitude ≈ full bar
|
||||
// When TX: show full red bar. When RX: scale RSSI to fill ratio
|
||||
let (fill_ratio, style) = if is_tx {
|
||||
(1.0_f32, filled_style)
|
||||
} else {
|
||||
let fill_ratio = (app.rssi / 0.6).min(1.0);
|
||||
(fill_ratio, filled_style)
|
||||
};
|
||||
let filled_rows = (inner.height as f32 * fill_ratio).round() as u16;
|
||||
|
||||
let filled_style = Style::default().fg(Color::Green);
|
||||
let empty_style = Style::default().fg(Color::DarkGray);
|
||||
|
||||
let mut lines = Vec::with_capacity(inner.height as usize);
|
||||
for r in 0..inner.height {
|
||||
let fill = r >= inner.height.saturating_sub(filled_rows);
|
||||
let (style, ch) = if fill {
|
||||
(filled_style, "█")
|
||||
let (s, line_style) = if fill {
|
||||
("█".repeat(inner.width as usize), style)
|
||||
} else {
|
||||
(empty_style, " ")
|
||||
(" ".repeat(inner.width as usize), empty_style)
|
||||
};
|
||||
let s = ch.repeat(inner.width as usize);
|
||||
lines.push(Line::from(Span::styled(s, style)));
|
||||
lines.push(Line::from(Span::styled(s, line_style)));
|
||||
}
|
||||
let paragraph = Paragraph::new(Text::from(lines));
|
||||
frame.render_widget(paragraph, inner);
|
||||
@@ -237,13 +252,16 @@ fn render_help_bar(frame: &mut Frame, area: Rect, app: &App) {
|
||||
InputMode::FobMetaYear
|
||||
| InputMode::FobMetaMake
|
||||
| InputMode::FobMetaModel
|
||||
| InputMode::FobMetaRegion => "Enter: Next Field | Esc: Cancel Export",
|
||||
| InputMode::FobMetaRegion
|
||||
| InputMode::FobMetaCommand => "Enter: Next Field | Esc: Cancel Export",
|
||||
InputMode::FobMetaNotes => "Enter: Save & Export | Esc: Cancel Export",
|
||||
InputMode::CaptureMetaYear
|
||||
| InputMode::CaptureMetaMake
|
||||
| InputMode::CaptureMetaModel => "Enter: Next Field | Esc: Cancel",
|
||||
InputMode::CaptureMetaRegion => "Enter: Save | Esc: Cancel",
|
||||
| InputMode::CaptureMetaModel
|
||||
| InputMode::CaptureMetaRegion => "Enter: Next Field | Esc: Cancel",
|
||||
InputMode::CaptureMetaCommand => "Enter: Save | Esc: Cancel",
|
||||
InputMode::License | InputMode::Credits => "Esc/Enter: Close | Up/Down: Scroll",
|
||||
InputMode::LoadFileBrowser => "Up/Down: Navigate | Enter: Open/Import | Esc: Close",
|
||||
};
|
||||
|
||||
let help = Paragraph::new(Line::from(Span::styled(
|
||||
@@ -261,6 +279,74 @@ fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {
|
||||
Rect::new(x, y, width.min(area.width), height.min(area.height))
|
||||
}
|
||||
|
||||
const LOAD_BROWSER_VISIBLE_ROWS: usize = 16;
|
||||
|
||||
/// Render the :load file browser overlay (centered, list of dirs and .fob/.sub files).
|
||||
fn render_load_file_browser(frame: &mut Frame, app: &App) {
|
||||
let area = frame.area();
|
||||
let popup_height = LOAD_BROWSER_VISIBLE_ROWS as u16 + 5;
|
||||
let popup_width = 56;
|
||||
let popup = centered_rect(popup_width, popup_height, area);
|
||||
|
||||
frame.render_widget(Clear, popup);
|
||||
|
||||
let path_str = app.load_browser_cwd.to_string_lossy();
|
||||
let path_display = if path_str.len() > popup_width as usize - 4 {
|
||||
format!("..{}", &path_str[path_str.len().saturating_sub(popup_width as usize - 5)..])
|
||||
} else {
|
||||
path_str.to_string()
|
||||
};
|
||||
|
||||
let mut items: Vec<ListItem> = Vec::new();
|
||||
items.push(ListItem::new(Line::from(Span::styled(
|
||||
format!(" {}", path_display),
|
||||
Style::default().fg(Color::DarkGray),
|
||||
))));
|
||||
items.push(ListItem::new(Line::from(Span::raw(""))));
|
||||
|
||||
let entries = &app.load_browser_entries;
|
||||
let scroll = app.load_browser_scroll;
|
||||
let selected = app.load_browser_selected.min(entries.len().saturating_sub(1));
|
||||
let end = (scroll + LOAD_BROWSER_VISIBLE_ROWS).min(entries.len());
|
||||
|
||||
for (i, (name, _path, is_dir)) in entries[scroll..end].iter().enumerate() {
|
||||
let idx = scroll + i;
|
||||
let is_selected = idx == selected;
|
||||
let prefix = if is_selected { " > " } else { " " };
|
||||
let (style, suffix) = if *is_dir {
|
||||
(
|
||||
if is_selected {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(Color::Cyan)
|
||||
},
|
||||
"/",
|
||||
)
|
||||
} else {
|
||||
(
|
||||
if is_selected {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(Color::White)
|
||||
},
|
||||
"",
|
||||
)
|
||||
};
|
||||
items.push(ListItem::new(Line::from(Span::styled(
|
||||
format!("{}{}{}", prefix, name, suffix),
|
||||
style,
|
||||
))));
|
||||
}
|
||||
|
||||
let block = Block::default()
|
||||
.title(" Load file (.fob / .sub) ")
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Color::Cyan));
|
||||
|
||||
let list = List::new(items).block(block);
|
||||
frame.render_widget(list, popup);
|
||||
}
|
||||
|
||||
/// Render the no-device warning (red box at startup when neither HackRF nor RTL-SDR found)
|
||||
fn render_hackrf_not_detected(frame: &mut Frame, _app: &App) {
|
||||
let area = frame.area();
|
||||
@@ -382,6 +468,7 @@ fn render_export_form(frame: &mut Frame, app: &App) {
|
||||
InputMode::FobMetaMake,
|
||||
InputMode::FobMetaModel,
|
||||
InputMode::FobMetaRegion,
|
||||
InputMode::FobMetaCommand,
|
||||
InputMode::FobMetaNotes,
|
||||
]
|
||||
} else {
|
||||
@@ -484,11 +571,17 @@ fn render_export_form(frame: &mut Frame, app: &App) {
|
||||
placeholder: "(e.g. NA, EU, APAC, MEA)",
|
||||
idx: 4,
|
||||
},
|
||||
FormField {
|
||||
label: " Command: ",
|
||||
value: &app.fob_meta_command,
|
||||
placeholder: "(e.g. Unlock, Lock, Trunk, Panic)",
|
||||
idx: 5,
|
||||
},
|
||||
FormField {
|
||||
label: " Notes: ",
|
||||
value: &app.fob_meta_notes,
|
||||
placeholder: "(optional — color, trim, VIN, etc.)",
|
||||
idx: 5,
|
||||
idx: 6,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -588,6 +681,7 @@ fn render_capture_meta_form(frame: &mut Frame, app: &App) {
|
||||
InputMode::CaptureMetaMake,
|
||||
InputMode::CaptureMetaModel,
|
||||
InputMode::CaptureMetaRegion,
|
||||
InputMode::CaptureMetaCommand,
|
||||
];
|
||||
let current_idx = field_modes
|
||||
.iter()
|
||||
@@ -644,7 +738,7 @@ fn render_capture_meta_form(frame: &mut Frame, app: &App) {
|
||||
placeholder: &'a str,
|
||||
idx: usize,
|
||||
}
|
||||
let fields: [FormField; 4] = [
|
||||
let fields: [FormField; 5] = [
|
||||
FormField {
|
||||
label: " Year: ",
|
||||
value: &app.capture_meta_year,
|
||||
@@ -669,6 +763,12 @@ fn render_capture_meta_form(frame: &mut Frame, app: &App) {
|
||||
placeholder: "(e.g. NA, EU, or ALL)",
|
||||
idx: 3,
|
||||
},
|
||||
FormField {
|
||||
label: " Command: ",
|
||||
value: &app.capture_meta_command,
|
||||
placeholder: "(e.g. Unlock, Lock, Trunk, Panic)",
|
||||
idx: 4,
|
||||
},
|
||||
];
|
||||
|
||||
for field in &fields {
|
||||
|
||||
+296
-17
@@ -1,38 +1,276 @@
|
||||
//! Vulnerability database: CVE entries matched by Year, Make, Model, Region.
|
||||
//! Vulnerability database: CVE entries matched by year range, makes, models, region.
|
||||
//! Used for "Vuln Found" column and the Vulnerability detail panel.
|
||||
//!
|
||||
//! Each row has a unique `id` so the same CVE can appear multiple times with different
|
||||
//! year/make/model scope (e.g. per-model year ranges for CVE-2022-37418).
|
||||
|
||||
/// One CVE entry. Year/Make/Model/Region use "ALL" to match any value.
|
||||
/// One vulnerability scope: a single row in the DB. Uniquely identified by `id`.
|
||||
/// The same CVE can appear in multiple entries with different year/make/model bounds.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VulnEntry {
|
||||
/// Unique id for this row (same CVE can have multiple rows with different scope). Used for tracking and future export/dedupe.
|
||||
#[allow(dead_code)]
|
||||
pub id: u32,
|
||||
pub cve: &'static str,
|
||||
pub year: &'static str,
|
||||
pub make: &'static str,
|
||||
pub model: &'static str,
|
||||
/// Inclusive start year (e.g. "2018"). "ALL" = no lower bound.
|
||||
pub year_start: &'static str,
|
||||
/// Inclusive end year (e.g. "2021"). "ALL" = no upper bound.
|
||||
pub year_end: &'static str,
|
||||
/// Makes that are affected (e.g. ["Renault"]). ["ALL"] = any make.
|
||||
pub makes: &'static [&'static str],
|
||||
/// Models that are affected (e.g. ["ZOE"], ["Civic"]). ["ALL"] = any model.
|
||||
pub models: &'static [&'static str],
|
||||
pub region: &'static str,
|
||||
pub description: &'static str,
|
||||
/// Source URL (e.g. NVD detail page) for further reading.
|
||||
pub url: &'static str,
|
||||
}
|
||||
|
||||
pub const VULN_DB: [VulnEntry; 2] = [
|
||||
pub const VULN_DB: [VulnEntry; 21] = [
|
||||
VulnEntry {
|
||||
id: 1,
|
||||
cve: "CVE-2022-38766",
|
||||
year: "2021",
|
||||
make: "Renault",
|
||||
model: "ALL",
|
||||
year_start: "2020",
|
||||
year_end: "2022",
|
||||
makes: &["Renault"],
|
||||
models: &["ZOE"],
|
||||
region: "ALL",
|
||||
description: "The remote keyless system on Renault ZOE 2021 vehicles sends 433.92 MHz RF signals from the same Rolling Codes set for each door-open request, which allows for a replay attack.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-38766",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 2,
|
||||
cve: "CVE-2022-27254",
|
||||
year: "2018",
|
||||
make: "Honda",
|
||||
model: "Civic",
|
||||
year_start: "2016",
|
||||
year_end: "2019",
|
||||
makes: &["Honda"],
|
||||
models: &["Civic"],
|
||||
region: "ALL",
|
||||
description: "The remote keyless system on Honda Civic 2018 vehicles sends the same RF signal for each door-open request, which allows for a replay attack, a related issue to CVE-2019-20626.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-27254",
|
||||
},
|
||||
// CVE-2022-37418 (RollBack): per make/model/year from research table (RKE RollBack variant)
|
||||
// Honda
|
||||
VulnEntry {
|
||||
id: 3,
|
||||
cve: "CVE-2022-37418",
|
||||
year_start: "2016",
|
||||
year_end: "2018",
|
||||
makes: &["Honda"],
|
||||
models: &["Fit (hybrid)", "Fit Hybrid"],
|
||||
region: "ALL",
|
||||
description: "RKE RollBack: unlock/resync after capturing two consecutive key fob signals. Attacker can unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-37418",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 4,
|
||||
cve: "CVE-2022-37418",
|
||||
year_start: "2018",
|
||||
year_end: "2018",
|
||||
makes: &["Honda"],
|
||||
models: &["Fit"],
|
||||
region: "ALL",
|
||||
description: "RKE RollBack: unlock/resync after capturing two consecutive key fob signals. Attacker can unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-37418",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 5,
|
||||
cve: "CVE-2022-37418",
|
||||
year_start: "2017",
|
||||
year_end: "2017",
|
||||
makes: &["Honda"],
|
||||
models: &["City"],
|
||||
region: "ALL",
|
||||
description: "RKE RollBack: unlock/resync after capturing two consecutive key fob signals. Attacker can unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-37418",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 6,
|
||||
cve: "CVE-2022-37418",
|
||||
year_start: "2016",
|
||||
year_end: "2022",
|
||||
makes: &["Honda"],
|
||||
models: &["Vezel"],
|
||||
region: "ALL",
|
||||
description: "RKE RollBack: unlock/resync after capturing two consecutive key fob signals. Attacker can unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-37418",
|
||||
},
|
||||
// Hyundai (Elantra 2013-2015 only; 2012 and Avante marked NO in table)
|
||||
VulnEntry {
|
||||
id: 7,
|
||||
cve: "CVE-2022-37418",
|
||||
year_start: "2013",
|
||||
year_end: "2015",
|
||||
makes: &["Hyundai"],
|
||||
models: &["Elantra"],
|
||||
region: "ALL",
|
||||
description: "RKE RollBack: unlock/resync after capturing two consecutive key fob signals. Attacker can unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-37418",
|
||||
},
|
||||
// Kia Cerato/Forte K3 (two year ranges in table)
|
||||
VulnEntry {
|
||||
id: 8,
|
||||
cve: "CVE-2022-37418",
|
||||
year_start: "2016",
|
||||
year_end: "2018",
|
||||
makes: &["Kia"],
|
||||
models: &["Cerato", "Forte", "K3"],
|
||||
region: "ALL",
|
||||
description: "RKE RollBack: unlock/resync after capturing two consecutive key fob signals. Attacker can unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-37418",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 9,
|
||||
cve: "CVE-2022-37418",
|
||||
year_start: "2012",
|
||||
year_end: "2018",
|
||||
makes: &["Kia"],
|
||||
models: &["Cerato", "Forte", "K3"],
|
||||
region: "ALL",
|
||||
description: "RKE RollBack: unlock/resync after capturing two consecutive key fob signals. Attacker can unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-37418",
|
||||
},
|
||||
// Mazda — CVE-2022-36945 (RollBack with three consecutive signals; through 2020)
|
||||
VulnEntry {
|
||||
id: 10,
|
||||
cve: "CVE-2022-36945",
|
||||
year_start: "2018",
|
||||
year_end: "2018",
|
||||
makes: &["Mazda"],
|
||||
models: &["3"],
|
||||
region: "ALL",
|
||||
description: "The RKE receiving unit on certain Mazda vehicles through 2020 allows remote attackers to perform unlock operations and force a resynchronization after capturing three consecutive valid key-fob signals over the radio, aka a RollBack attack. The attacker retains the ability to unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-36945",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 11,
|
||||
cve: "CVE-2022-36945",
|
||||
year_start: "2018",
|
||||
year_end: "2018",
|
||||
makes: &["Mazda"],
|
||||
models: &["2 Sedan"],
|
||||
region: "ALL",
|
||||
description: "The RKE receiving unit on certain Mazda vehicles through 2020 allows remote attackers to perform unlock operations and force a resynchronization after capturing three consecutive valid key-fob signals over the radio, aka a RollBack attack. The attacker retains the ability to unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-36945",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 12,
|
||||
cve: "CVE-2022-36945",
|
||||
year_start: "2020",
|
||||
year_end: "2020",
|
||||
makes: &["Mazda"],
|
||||
models: &["2 HB (facelift)", "2 HB"],
|
||||
region: "ALL",
|
||||
description: "The RKE receiving unit on certain Mazda vehicles through 2020 allows remote attackers to perform unlock operations and force a resynchronization after capturing three consecutive valid key-fob signals over the radio, aka a RollBack attack. The attacker retains the ability to unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-36945",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 13,
|
||||
cve: "CVE-2022-36945",
|
||||
year_start: "2019",
|
||||
year_end: "2019",
|
||||
makes: &["Mazda"],
|
||||
models: &["Cx-3", "CX-3"],
|
||||
region: "ALL",
|
||||
description: "The RKE receiving unit on certain Mazda vehicles through 2020 allows remote attackers to perform unlock operations and force a resynchronization after capturing three consecutive valid key-fob signals over the radio, aka a RollBack attack. The attacker retains the ability to unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-36945",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 14,
|
||||
cve: "CVE-2022-36945",
|
||||
year_start: "2018",
|
||||
year_end: "2018",
|
||||
makes: &["Mazda"],
|
||||
models: &["Cx-5", "CX-5"],
|
||||
region: "ALL",
|
||||
description: "The RKE receiving unit on certain Mazda vehicles through 2020 allows remote attackers to perform unlock operations and force a resynchronization after capturing three consecutive valid key-fob signals over the radio, aka a RollBack attack. The attacker retains the ability to unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-36945",
|
||||
},
|
||||
// Nissan (Latio, Sylphy; Teana and Wish marked NO in table — excluded)
|
||||
VulnEntry {
|
||||
id: 15,
|
||||
cve: "CVE-2022-37418",
|
||||
year_start: "2007",
|
||||
year_end: "2012",
|
||||
makes: &["Nissan"],
|
||||
models: &["Latio"],
|
||||
region: "ALL",
|
||||
description: "RKE RollBack: unlock/resync after capturing two consecutive key fob signals. Attacker can unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-37418",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 16,
|
||||
cve: "CVE-2022-37418",
|
||||
year_start: "2012",
|
||||
year_end: "2019",
|
||||
makes: &["Nissan"],
|
||||
models: &["Sylphy"],
|
||||
region: "ALL",
|
||||
description: "RKE RollBack: unlock/resync after capturing two consecutive key fob signals. Attacker can unlock indefinitely.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2022-37418",
|
||||
},
|
||||
// CVE-2019-20626: Honda/Acura static code replay (no rolling code). Confirmed vehicles per Unoriginal-Rice-Patty.
|
||||
VulnEntry {
|
||||
id: 17,
|
||||
cve: "CVE-2019-20626",
|
||||
year_start: "2009",
|
||||
year_end: "2009",
|
||||
makes: &["Acura"],
|
||||
models: &["TSX"],
|
||||
region: "ALL",
|
||||
description: "The remote keyless system sends the same RF signal for each door-open request, which might allow a replay attack. Honda/Acura use static codes (no rolling code).",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2019-20626",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 18,
|
||||
cve: "CVE-2019-20626",
|
||||
year_start: "2016",
|
||||
year_end: "2016",
|
||||
makes: &["Honda"],
|
||||
models: &["Accord"],
|
||||
region: "ALL",
|
||||
description: "The remote keyless system sends the same RF signal for each door-open request, which might allow a replay attack. Honda/Acura use static codes (no rolling code).",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2019-20626",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 19,
|
||||
cve: "CVE-2019-20626",
|
||||
year_start: "2017",
|
||||
year_end: "2017",
|
||||
makes: &["Honda"],
|
||||
models: &["HR-V"],
|
||||
region: "ALL",
|
||||
description: "The remote keyless system on Honda HR-V 2017 vehicles sends the same RF signal for each door-open request, which might allow a replay attack.",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2019-20626",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 20,
|
||||
cve: "CVE-2019-20626",
|
||||
year_start: "2018",
|
||||
year_end: "2018",
|
||||
makes: &["Honda"],
|
||||
models: &["Civic"],
|
||||
region: "ALL",
|
||||
description: "The remote keyless system sends the same RF signal for each door-open request, which might allow a replay attack. Honda/Acura use static codes (no rolling code).",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2019-20626",
|
||||
},
|
||||
VulnEntry {
|
||||
id: 21,
|
||||
cve: "CVE-2019-20626",
|
||||
year_start: "2020",
|
||||
year_end: "2020",
|
||||
makes: &["Honda"],
|
||||
models: &["Civic"],
|
||||
region: "ALL",
|
||||
description: "The remote keyless system sends the same RF signal for each door-open request, which might allow a replay attack. Honda/Acura use static codes (no rolling code).",
|
||||
url: "https://nvd.nist.gov/vuln/detail/CVE-2019-20626",
|
||||
},
|
||||
];
|
||||
|
||||
/// Match a capture's year/make/model/region against the DB. "ALL" in an entry matches any value.
|
||||
/// Empty/None from capture is treated as empty string (so "ALL" matches it).
|
||||
/// Match a capture's year/make/model/region against the DB.
|
||||
/// Year: parsed as number; must be in [year_start, year_end] when entry has bounds.
|
||||
/// Make/Model: capture value must match one of the entry's list, or entry list contains "ALL".
|
||||
/// Region: "ALL" in entry matches any; otherwise case-insensitive match.
|
||||
pub fn match_vulns(
|
||||
year: Option<&str>,
|
||||
make: Option<&str>,
|
||||
@@ -44,17 +282,58 @@ pub fn match_vulns(
|
||||
let mod_ = model.unwrap_or("");
|
||||
let r = region.unwrap_or("");
|
||||
|
||||
let year_num: Option<u32> = y.trim().parse().ok();
|
||||
|
||||
VULN_DB
|
||||
.iter()
|
||||
.filter(|e| {
|
||||
(e.year == "ALL" || eq_ignore_case(e.year, y))
|
||||
&& (e.make == "ALL" || eq_ignore_case(e.make, m))
|
||||
&& (e.model == "ALL" || eq_ignore_case(e.model, mod_))
|
||||
year_in_range(e.year_start, e.year_end, year_num)
|
||||
&& list_matches(e.makes, m)
|
||||
&& list_matches(e.models, mod_)
|
||||
&& (e.region == "ALL" || eq_ignore_case(e.region, r))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn year_in_range(start: &str, end: &str, capture_year: Option<u32>) -> bool {
|
||||
let has_start = start != "ALL" && !start.trim().is_empty();
|
||||
let has_end = end != "ALL" && !end.trim().is_empty();
|
||||
if !has_start && !has_end {
|
||||
return true;
|
||||
}
|
||||
let Some(yr) = capture_year else {
|
||||
return false;
|
||||
};
|
||||
if has_start {
|
||||
let Ok(s) = start.trim().parse::<u32>() else {
|
||||
return false;
|
||||
};
|
||||
if yr < s {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if has_end {
|
||||
let Ok(e) = end.trim().parse::<u32>() else {
|
||||
return false;
|
||||
};
|
||||
if yr > e {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn list_matches(list: &[&'static str], capture_value: &str) -> bool {
|
||||
if list.is_empty() {
|
||||
return false;
|
||||
}
|
||||
if list.iter().any(|s| *s == "ALL") {
|
||||
return true;
|
||||
}
|
||||
list.iter()
|
||||
.any(|s| eq_ignore_case(s, capture_value))
|
||||
}
|
||||
|
||||
fn eq_ignore_case(a: &str, b: &str) -> bool {
|
||||
a.eq_ignore_ascii_case(b)
|
||||
}
|
||||
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "libhackrf"
|
||||
version = "0.1.1"
|
||||
authors = ["Connor Slade <connor@connorcode.com>"]
|
||||
description = "A modern libhackrf wrapper that supports receiving and transmitting. (Patched for ARM cross-compilation)"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/connorslade/libhackrf-rs"
|
||||
|
||||
[lib]
|
||||
name = "libhackrf"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies.num-complex]
|
||||
version = "0.4.6"
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
pub enum DeviceType {
|
||||
Jellybean = 0,
|
||||
Jawbreaker = 1,
|
||||
/// HackRF One prior to r9
|
||||
Hackrf1Og = 2,
|
||||
Rad1O = 3,
|
||||
Hackrf1R9 = 4,
|
||||
/// Tried detection but did not recognize board
|
||||
Unrecognized = 0xFE,
|
||||
/// detection not yet attempted
|
||||
Undetected = 0xFF,
|
||||
}
|
||||
|
||||
impl DeviceType {
|
||||
pub fn from_id(id: u8) -> Self {
|
||||
match id {
|
||||
0 => DeviceType::Jellybean,
|
||||
1 => DeviceType::Jawbreaker,
|
||||
2 => DeviceType::Hackrf1Og,
|
||||
3 => DeviceType::Rad1O,
|
||||
4 => DeviceType::Hackrf1R9,
|
||||
0xFE => DeviceType::Unrecognized,
|
||||
0xFF => DeviceType::Undetected,
|
||||
_ => DeviceType::Undetected,
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, HackrfError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HackrfError {
|
||||
InvalidParam = -2,
|
||||
NotFound = -5,
|
||||
Busy = -6,
|
||||
NoMem = -11,
|
||||
Libusb = -1000,
|
||||
Thread = -1001,
|
||||
StreamingThreadErr = -1002,
|
||||
StreamingStopped = -1003,
|
||||
StreamingExitCalled = -1004,
|
||||
Other = -9999,
|
||||
}
|
||||
|
||||
impl HackrfError {
|
||||
pub fn from_id(id: i32) -> Result<()> {
|
||||
Err(match id {
|
||||
0 | 1 => return Ok(()),
|
||||
-2 => HackrfError::InvalidParam,
|
||||
-5 => HackrfError::NotFound,
|
||||
-6 => HackrfError::Busy,
|
||||
-11 => HackrfError::NoMem,
|
||||
-1000 => HackrfError::Libusb,
|
||||
-1001 => HackrfError::Thread,
|
||||
-1002 => HackrfError::StreamingThreadErr,
|
||||
-1003 => HackrfError::StreamingStopped,
|
||||
-1004 => HackrfError::StreamingExitCalled,
|
||||
-9999 => HackrfError::Other,
|
||||
_ => HackrfError::Other,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HackrfError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_fmt(format_args!("{self:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for HackrfError {}
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
#![allow(improper_ctypes)]
|
||||
|
||||
use std::ffi::{c_char, c_double, c_int, c_uchar, c_uint, c_ulonglong, c_void};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct HackrfDevice;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct HackrfTransfer {
|
||||
pub device: *mut HackrfDevice,
|
||||
pub buffer: *mut c_uchar,
|
||||
pub buffer_length: c_int,
|
||||
pub valid_length: c_int,
|
||||
pub rx_ctx: *mut c_void,
|
||||
pub tx_ctx: *mut c_void,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[repr(C)]
|
||||
pub struct SerialNumber {
|
||||
pub part_id: [c_uint; 2],
|
||||
pub serial_no: [c_uint; 4],
|
||||
}
|
||||
|
||||
#[link(name = "hackrf")]
|
||||
extern "C" {
|
||||
pub fn hackrf_init() -> c_int;
|
||||
pub fn hackrf_exit() -> c_int;
|
||||
|
||||
pub fn hackrf_open(device: *mut *mut HackrfDevice) -> c_int;
|
||||
pub fn hackrf_close(device: *mut HackrfDevice) -> c_int;
|
||||
|
||||
pub fn hackrf_start_rx(
|
||||
device: *mut HackrfDevice,
|
||||
callback: extern "C" fn(*mut HackrfTransfer) -> c_int,
|
||||
rx_ctx: *mut c_void,
|
||||
) -> c_int;
|
||||
pub fn hackrf_stop_rx(device: *mut HackrfDevice) -> c_int;
|
||||
|
||||
pub fn hackrf_start_tx(
|
||||
device: *mut HackrfDevice,
|
||||
callback: extern "C" fn(*mut HackrfTransfer) -> c_int,
|
||||
tx_ctx: *mut c_void,
|
||||
) -> c_int;
|
||||
pub fn hackrf_stop_tx(device: *mut HackrfDevice) -> c_int;
|
||||
|
||||
pub fn hackrf_is_streaming(device: *mut HackrfDevice) -> c_int;
|
||||
|
||||
pub fn hackrf_set_baseband_filter_bandwidth(
|
||||
device: *mut HackrfDevice,
|
||||
bandwidth_hz: c_uint,
|
||||
) -> c_int;
|
||||
|
||||
pub fn hackrf_board_id_read(device: *mut HackrfDevice, value: *mut c_uchar) -> c_int;
|
||||
pub fn hackrf_version_string_read(
|
||||
device: *mut HackrfDevice,
|
||||
version: *mut c_char,
|
||||
length: c_uchar,
|
||||
) -> c_int;
|
||||
pub fn hackrf_board_partid_serialno_read(
|
||||
device: *mut HackrfDevice,
|
||||
read_partid_serialno: *mut SerialNumber,
|
||||
) -> c_int;
|
||||
|
||||
pub fn hackrf_set_freq(device: *mut HackrfDevice, freq_hz: c_ulonglong) -> c_int;
|
||||
pub fn hackrf_set_freq_explicit(
|
||||
device: *mut HackrfDevice,
|
||||
if_freq_hz: c_ulonglong,
|
||||
lo_freq_hz: c_ulonglong,
|
||||
path: c_uint,
|
||||
) -> c_int;
|
||||
|
||||
pub fn hackrf_set_sample_rate_manual(
|
||||
device: *mut HackrfDevice,
|
||||
freq_hz: c_uint,
|
||||
divider: c_uint,
|
||||
) -> c_int;
|
||||
pub fn hackrf_set_sample_rate(device: *mut HackrfDevice, freq_hz: c_double) -> c_int;
|
||||
|
||||
pub fn hackrf_set_amp_enable(device: *mut HackrfDevice, value: c_uchar) -> c_int;
|
||||
|
||||
pub fn hackrf_set_lna_gain(device: *mut HackrfDevice, value: c_uint) -> c_int;
|
||||
pub fn hackrf_set_vga_gain(device: *mut HackrfDevice, value: c_uint) -> c_int;
|
||||
pub fn hackrf_set_txvga_gain(device: *mut HackrfDevice, value: c_uint) -> c_int;
|
||||
|
||||
pub fn hackrf_set_antenna_enable(device: *mut HackrfDevice, value: c_uchar) -> c_int;
|
||||
|
||||
pub fn hackrf_error_name(errcode: c_int) -> *const c_char;
|
||||
pub fn hackrf_board_id_name(hackrf_board_id: c_uchar) -> *const c_char;
|
||||
pub fn hackrf_filter_path_name(path: c_uint) -> *const c_char;
|
||||
|
||||
pub fn hackrf_compute_baseband_filter_bw_round_down_lt(bandwidth_hz: c_uint) -> c_uint;
|
||||
pub fn hackrf_compute_baseband_filter_bw(bandwidth_hz: c_uint) -> c_uint;
|
||||
}
|
||||
Vendored
+210
@@ -0,0 +1,210 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
ffi::c_void,
|
||||
mem, ptr, slice,
|
||||
sync::{
|
||||
atomic::{AtomicPtr, AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
mod enums;
|
||||
pub mod error;
|
||||
pub mod ffi;
|
||||
pub use enums::DeviceType;
|
||||
mod transfer;
|
||||
pub mod util;
|
||||
|
||||
use error::{HackrfError, Result};
|
||||
use ffi::SerialNumber;
|
||||
use transfer::{rx_callback, tx_callback, ReceiveCallback, TransferContext, TransmitCallback};
|
||||
|
||||
pub mod exports {
|
||||
pub use num_complex;
|
||||
}
|
||||
|
||||
static DEVICE_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
/// A HackRf device.
|
||||
#[derive(Clone)]
|
||||
pub struct HackRf {
|
||||
inner: Arc<HackRfInner>,
|
||||
}
|
||||
|
||||
struct HackRfInner {
|
||||
device: *mut ffi::HackrfDevice,
|
||||
user_data: AtomicPtr<c_void>,
|
||||
}
|
||||
|
||||
impl HackRf {
|
||||
/// Connects to a HackRF device.
|
||||
pub fn open() -> Result<HackRf> {
|
||||
if DEVICE_COUNT.fetch_add(1, Ordering::Relaxed) == 0 {
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_init())? }
|
||||
}
|
||||
|
||||
let mut device = std::ptr::null_mut();
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_open(&mut device))? }
|
||||
|
||||
Ok(Self {
|
||||
inner: Arc::new(HackRfInner {
|
||||
device,
|
||||
user_data: AtomicPtr::new(ptr::null_mut()),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the internial representation of the HackRF device. This can be used
|
||||
/// with unsafe FFI functions if needed.
|
||||
#[inline(always)]
|
||||
pub fn device(&self) -> *mut ffi::HackrfDevice {
|
||||
self.inner.device
|
||||
}
|
||||
|
||||
/// Gets the device serial number.
|
||||
pub fn get_serial_number(&self) -> Result<SerialNumber> {
|
||||
let mut serial_number = SerialNumber::default();
|
||||
unsafe {
|
||||
HackrfError::from_id(ffi::hackrf_board_partid_serialno_read(
|
||||
self.device(),
|
||||
&mut serial_number,
|
||||
))?
|
||||
}
|
||||
Ok(serial_number)
|
||||
}
|
||||
|
||||
/// Read hackrf_board_id from a device and convert it to a DeviceType.
|
||||
pub fn get_device_type(&self) -> Result<DeviceType> {
|
||||
let mut value = 0;
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_board_id_read(self.device(), &mut value)) }?;
|
||||
Ok(DeviceType::from_id(value))
|
||||
}
|
||||
|
||||
/// Read HackRF firmware version as a string.
|
||||
pub fn version(&self) -> String {
|
||||
let mut version = vec![0; 32];
|
||||
|
||||
unsafe {
|
||||
ffi::hackrf_version_string_read(
|
||||
self.device(),
|
||||
version.as_mut_ptr(),
|
||||
version.len() as u8,
|
||||
);
|
||||
}
|
||||
|
||||
let end = version
|
||||
.iter()
|
||||
.position(|&x| x == 0)
|
||||
.unwrap_or(version.len());
|
||||
|
||||
// Use from_raw_parts to handle both c_char=i8 (x86) and c_char=u8 (ARM)
|
||||
let version = unsafe { slice::from_raw_parts(version.as_ptr() as *const u8, end) };
|
||||
String::from_utf8_lossy(version).into_owned()
|
||||
}
|
||||
|
||||
/// Sets the center frequency in Hz.
|
||||
pub fn set_freq(&self, freq: u64) -> Result<()> {
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_set_freq(self.device(), freq)) }
|
||||
}
|
||||
|
||||
/// Sets the sample rate in Hz.
|
||||
pub fn set_sample_rate(&self, sample_rate: u32) -> Result<()> {
|
||||
unsafe {
|
||||
HackrfError::from_id(ffi::hackrf_set_sample_rate_manual(
|
||||
self.device(),
|
||||
sample_rate,
|
||||
1,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the state of the externial amplifier.
|
||||
pub fn set_amp_enable(&self, enable: bool) -> Result<()> {
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_set_amp_enable(self.device(), enable as u8)) }
|
||||
}
|
||||
|
||||
/// Low noise amplifier gain.
|
||||
/// Between 0d and 40d in steps of 8dB.
|
||||
pub fn set_lna_gain(&self, gain: u32) -> Result<()> {
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_set_lna_gain(self.device(), gain)) }
|
||||
}
|
||||
|
||||
/// Variable gain amplifier. Range 0-62 (step 2dB).
|
||||
pub fn set_rxvga_gain(&self, gain: u32) -> Result<()> {
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_set_vga_gain(self.device(), gain)) }
|
||||
}
|
||||
|
||||
/// Transmit variable gain amplifier. Range 0-47 (step 1dB).
|
||||
pub fn set_txvga_gain(&self, gain: u32) -> Result<()> {
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_set_txvga_gain(self.device(), gain)) }
|
||||
}
|
||||
|
||||
pub fn set_baseband_filter_bandwidth(&self, bandwidth_hz: u32) -> Result<()> {
|
||||
unsafe {
|
||||
HackrfError::from_id(ffi::hackrf_set_baseband_filter_bandwidth(
|
||||
self.device(),
|
||||
ffi::hackrf_compute_baseband_filter_bw(bandwidth_hz),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts transmitting samples from the device.
|
||||
pub fn start_tx(&self, callback: TransmitCallback, user_data: impl Any) -> Result<()> {
|
||||
let context = TransferContext::new(callback, self.clone(), Box::new(user_data));
|
||||
let callback = Box::leak(Box::new(context)) as *mut _ as *mut _;
|
||||
self.inner.user_data.store(callback, Ordering::Relaxed);
|
||||
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_start_tx(self.device(), tx_callback, callback)) }
|
||||
}
|
||||
|
||||
/// Stops the current transmit operation.
|
||||
pub fn stop_tx(&self) -> Result<()> {
|
||||
let user_data = &self.inner.user_data;
|
||||
let callback = user_data.swap(ptr::null_mut(), Ordering::Relaxed);
|
||||
if !callback.is_null() {
|
||||
let callback = unsafe { Box::from_raw(callback as *mut fn(*mut ffi::HackrfTransfer)) };
|
||||
drop(callback);
|
||||
}
|
||||
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_stop_tx(self.device())) }
|
||||
}
|
||||
|
||||
/// Starts receiving samples from the device.
|
||||
pub fn start_rx(&self, callback: ReceiveCallback, user_data: impl Any + Sync) -> Result<()> {
|
||||
let context = TransferContext::new(callback, self.clone(), Box::new(user_data));
|
||||
let callback = Box::leak(Box::new(context)) as *mut _ as *mut _;
|
||||
self.inner.user_data.store(callback, Ordering::Relaxed);
|
||||
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_start_rx(self.device(), rx_callback, callback)) }
|
||||
}
|
||||
|
||||
/// Stops the current receive operation.
|
||||
pub fn stop_rx(&self) -> Result<()> {
|
||||
let user_data = &self.inner.user_data;
|
||||
let callback = user_data.swap(ptr::null_mut(), Ordering::Relaxed);
|
||||
if !callback.is_null() {
|
||||
let callback = unsafe { Box::from_raw(callback as *mut fn(*mut ffi::HackrfTransfer)) };
|
||||
drop(callback);
|
||||
}
|
||||
|
||||
unsafe { HackrfError::from_id(ffi::hackrf_stop_rx(self.device())) }
|
||||
}
|
||||
|
||||
/// Returns true if the device is currently streaming samples (transmitting or receiving).
|
||||
pub fn is_streaming(&self) -> bool {
|
||||
unsafe { ffi::hackrf_is_streaming(self.device()) != 0 }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for HackRfInner {}
|
||||
unsafe impl Sync for HackRfInner {}
|
||||
|
||||
impl Drop for HackRf {
|
||||
fn drop(&mut self) {
|
||||
let _ = unsafe { HackrfError::from_id(ffi::hackrf_close(self.device())) };
|
||||
|
||||
if DEVICE_COUNT.fetch_sub(1, Ordering::Relaxed) == 1 {
|
||||
let _ = unsafe { HackrfError::from_id(ffi::hackrf_exit()) };
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
use std::{any::Any, slice};
|
||||
|
||||
use num_complex::Complex;
|
||||
|
||||
use super::{ffi, HackRf};
|
||||
|
||||
pub type TransmitCallback = fn(hack_rf: &HackRf, samples: &mut [Complex<i8>], user: &dyn Any);
|
||||
pub type ReceiveCallback = fn(hack_rf: &HackRf, samples: &[Complex<i8>], user: &dyn Any);
|
||||
|
||||
pub struct TransferContext<Callback> {
|
||||
callback: Callback,
|
||||
hackrf: HackRf,
|
||||
user_data: Box<dyn Any>,
|
||||
}
|
||||
|
||||
impl<Callback> TransferContext<Callback> {
|
||||
pub(super) fn new(callback: Callback, hackrf: HackRf, user_data: Box<dyn Any>) -> Self {
|
||||
Self {
|
||||
callback,
|
||||
hackrf,
|
||||
user_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) extern "C" fn tx_callback(transfer: *mut ffi::HackrfTransfer) -> i32 {
|
||||
unsafe {
|
||||
let transfer = &mut *transfer;
|
||||
let context = &*(transfer.tx_ctx as *mut TransferContext<TransmitCallback>);
|
||||
|
||||
let buffer = slice::from_raw_parts_mut(
|
||||
transfer.buffer as *mut Complex<i8>,
|
||||
transfer.valid_length as usize / 2,
|
||||
);
|
||||
(context.callback)(&context.hackrf, buffer, &*context.user_data);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
pub(super) extern "C" fn rx_callback(transfer: *mut ffi::HackrfTransfer) -> i32 {
|
||||
unsafe {
|
||||
let transfer = &*transfer;
|
||||
let context = &*(transfer.rx_ctx as *mut TransferContext<ReceiveCallback>);
|
||||
|
||||
let buffer = slice::from_raw_parts(
|
||||
transfer.buffer as *const Complex<i8>,
|
||||
transfer.valid_length as usize / 2,
|
||||
);
|
||||
(context.callback)(&context.hackrf, buffer, &*context.user_data);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user