fob export fixes

This commit is contained in:
KaraZajac
2026-02-22 21:37:56 -05:00
parent 334491db4e
commit 5d0bd97954
3 changed files with 77 additions and 69 deletions
Generated
+1 -1
View File
@@ -444,7 +444,7 @@ dependencies = [
[[package]]
name = "kat"
version = "1.1.2"
version = "1.1.3"
dependencies = [
"anyhow",
"atty",
+22 -5
View File
@@ -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
@@ -213,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
@@ -268,7 +274,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
@@ -314,6 +320,17 @@ src/
└── status_bar.rs # Status bar
```
## Call for researchers
**We need your help.** KATs 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.
+17 -26
View File
@@ -1307,10 +1307,9 @@ impl App {
.unwrap_or_else(|_| path.to_string_lossy().to_string())
}
/// For unknown protocol: Year_Make_Model_Region_Command (user can edit; we append _<8 hex> on save).
/// For known protocol: protocol_serial as before.
/// 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 {
if capture.protocol_name().eq_ignore_ascii_case("unknown") {
let year = capture
.year
.as_deref()
@@ -1320,9 +1319,17 @@ impl App {
let make = capture
.make
.as_deref()
.unwrap_or("Unknown")
.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(' ', "_");
.replace(' ', "_")
}
});
let model = capture
.model
.as_deref()
@@ -1346,13 +1353,6 @@ impl App {
cmd_str.replace(' ', "_")
};
format!("{}_{}_{}_{}_{}", year, make, model, region, command)
} else {
format!(
"{}_{}",
capture.protocol_name().replace(' ', "_").to_lowercase(),
capture.serial_hex()
)
}
}
/// Start .fob export by entering filename input mode
@@ -1362,21 +1362,17 @@ impl App {
return Ok(());
}
// Pre-fill filename from protocol + serial; for unknown captures include 8-hex suffix so user sees it
// 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));
self.export_filename = if capture.map(|c| c.protocol.is_none()).unwrap_or(false) {
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;
format!("{}_{:08X}", default_name, suffix)
} else {
default_name.clone()
};
self.export_filename = format!("{}_{:08X}", default_name, suffix);
// Pre-fill metadata from capture if set, otherwise make from protocol
let make = capture
@@ -1449,18 +1445,15 @@ impl App {
notes: self.fob_meta_notes.clone(),
};
// Unknown captures or user-edited vehicle-style filenames always get 8-hex suffix to avoid overwrites.
let use_hex_suffix = capture.protocol.is_none()
|| self.export_filename.ends_with("_Unknown")
|| self.export_filename == "Unknown";
// 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 use_hex_suffix && already_has_8hex {
let filename = if already_has_8hex {
format!("{}.fob", self.export_filename.trim())
} else if use_hex_suffix {
} else {
let base = self.export_filename.trim();
let suffix_nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
@@ -1473,8 +1466,6 @@ impl App {
} else {
format!("{}_{}.fob", base, hex_suffix)
}
} else {
format!("{}.fob", self.export_filename)
};
let path = export_dir.join(&filename);