Allow missing fields
This commit is contained in:
26
src/db.rs
26
src/db.rs
@@ -24,16 +24,16 @@ impl Database {
|
|||||||
timestamp TEXT NOT NULL,
|
timestamp TEXT NOT NULL,
|
||||||
app TEXT NOT NULL,
|
app TEXT NOT NULL,
|
||||||
version TEXT NOT NULL,
|
version TEXT NOT NULL,
|
||||||
offline_login_usage INTEGER NOT NULL,
|
offline_login_usage INTEGER,
|
||||||
is_password_autofill_enabled INTEGER NOT NULL,
|
is_password_autofill_enabled INTEGER,
|
||||||
camera_roll_usage INTEGER NOT NULL,
|
camera_roll_usage INTEGER,
|
||||||
os TEXT NOT NULL,
|
os TEXT,
|
||||||
app_name TEXT NOT NULL,
|
app_name TEXT,
|
||||||
touch_id INTEGER NOT NULL,
|
touch_id INTEGER,
|
||||||
is_offline_login_enabled INTEGER NOT NULL,
|
is_offline_login_enabled INTEGER,
|
||||||
model TEXT NOT NULL,
|
model TEXT,
|
||||||
device TEXT NOT NULL,
|
device TEXT,
|
||||||
password_autofill_usage INTEGER NOT NULL
|
password_autofill_usage INTEGER
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_session_id ON signature_entries(session_id);
|
CREATE INDEX IF NOT EXISTS idx_session_id ON signature_entries(session_id);
|
||||||
@@ -66,12 +66,12 @@ impl Database {
|
|||||||
entry.app,
|
entry.app,
|
||||||
entry.version,
|
entry.version,
|
||||||
entry.offline_login_usage,
|
entry.offline_login_usage,
|
||||||
entry.is_password_autofill_enabled as i32,
|
entry.is_password_autofill_enabled.map(|b| b as i32),
|
||||||
entry.camera_roll_usage,
|
entry.camera_roll_usage,
|
||||||
entry.os,
|
entry.os,
|
||||||
entry.app_name,
|
entry.app_name,
|
||||||
entry.touch_id as i32,
|
entry.touch_id.map(|b| b as i32),
|
||||||
entry.is_offline_login_enabled as i32,
|
entry.is_offline_login_enabled.map(|b| b as i32),
|
||||||
entry.model,
|
entry.model,
|
||||||
entry.device,
|
entry.device,
|
||||||
entry.password_autofill_usage,
|
entry.password_autofill_usage,
|
||||||
|
|||||||
167
src/parser.rs
167
src/parser.rs
@@ -4,22 +4,22 @@ use regex::Regex;
|
|||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
/// Represents a parsed signature log entry
|
/// Represents a parsed signature log entry
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct SignatureEntry {
|
pub struct SignatureEntry {
|
||||||
pub session_id: String,
|
pub session_id: String,
|
||||||
pub timestamp: NaiveDateTime,
|
pub timestamp: NaiveDateTime,
|
||||||
pub app: String,
|
pub app: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub offline_login_usage: i64,
|
pub offline_login_usage: Option<i64>,
|
||||||
pub is_password_autofill_enabled: bool,
|
pub is_password_autofill_enabled: Option<bool>,
|
||||||
pub camera_roll_usage: i64,
|
pub camera_roll_usage: Option<i64>,
|
||||||
pub os: String,
|
pub os: Option<String>,
|
||||||
pub app_name: String,
|
pub app_name: Option<String>,
|
||||||
pub touch_id: bool,
|
pub touch_id: Option<bool>,
|
||||||
pub is_offline_login_enabled: bool,
|
pub is_offline_login_enabled: Option<bool>,
|
||||||
pub model: String,
|
pub model: Option<String>,
|
||||||
pub device: String,
|
pub device: Option<String>,
|
||||||
pub password_autofill_usage: i64,
|
pub password_autofill_usage: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for parsing different message types from logs.
|
/// Trait for parsing different message types from logs.
|
||||||
@@ -93,16 +93,16 @@ impl SignatureParser {
|
|||||||
timestamp,
|
timestamp,
|
||||||
app,
|
app,
|
||||||
version,
|
version,
|
||||||
offline_login_usage: parse_number(&details, "offlineLoginUsage")?,
|
offline_login_usage: parse_number(&details, "offlineLoginUsage"),
|
||||||
is_password_autofill_enabled: parse_bool(&details, "isPasswordAutofillEnabled")?,
|
is_password_autofill_enabled: parse_bool(&details, "isPasswordAutofillEnabled"),
|
||||||
camera_roll_usage: parse_number(&details, "cameraRollUsage")?,
|
camera_roll_usage: parse_number(&details, "cameraRollUsage"),
|
||||||
os: get_string(&details, "OS")?,
|
os: get_string(&details, "OS"),
|
||||||
app_name: get_string(&details, "appName")?,
|
app_name: get_string(&details, "appName"),
|
||||||
touch_id: parse_bool(&details, "touchID")?,
|
touch_id: parse_bool(&details, "touchID"),
|
||||||
is_offline_login_enabled: parse_bool(&details, "isOfflineLoginEnabled")?,
|
is_offline_login_enabled: parse_bool(&details, "isOfflineLoginEnabled"),
|
||||||
model: get_string(&details, "model")?,
|
model: get_string(&details, "model"),
|
||||||
device: get_string(&details, "device")?,
|
device: get_string(&details, "device"),
|
||||||
password_autofill_usage: parse_number(&details, "passwordAutofillUsage")?,
|
password_autofill_usage: parse_number(&details, "passwordAutofillUsage"),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ParsedMessage::Signature(entry))
|
Ok(ParsedMessage::Signature(entry))
|
||||||
@@ -168,28 +168,22 @@ fn parse_details(details: &str) -> Result<std::collections::HashMap<String, Stri
|
|||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_number(map: &std::collections::HashMap<String, String>, key: &str) -> Result<i64> {
|
fn parse_number(map: &std::collections::HashMap<String, String>, key: &str) -> Option<i64> {
|
||||||
map.get(key)
|
map.get(key).and_then(|v| v.parse().ok())
|
||||||
.ok_or_else(|| anyhow!("Missing key: {}", key))?
|
|
||||||
.parse()
|
|
||||||
.map_err(|e| anyhow!("Invalid number for {}: {}", key, e))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_bool(map: &std::collections::HashMap<String, String>, key: &str) -> Result<bool> {
|
fn parse_bool(map: &std::collections::HashMap<String, String>, key: &str) -> Option<bool> {
|
||||||
let value = map
|
map.get(key).and_then(|value| {
|
||||||
.get(key)
|
|
||||||
.ok_or_else(|| anyhow!("Missing key: {}", key))?;
|
|
||||||
match value.to_lowercase().as_str() {
|
match value.to_lowercase().as_str() {
|
||||||
"yes" | "true" | "1" => Ok(true),
|
"yes" | "true" | "1" => Some(true),
|
||||||
"no" | "false" | "0" => Ok(false),
|
"no" | "false" | "0" => Some(false),
|
||||||
_ => Err(anyhow!("Invalid boolean for {}: {}", key, value)),
|
_ => None,
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_string(map: &std::collections::HashMap<String, String>, key: &str) -> Result<String> {
|
fn get_string(map: &std::collections::HashMap<String, String>, key: &str) -> Option<String> {
|
||||||
map.get(key)
|
map.get(key).map(|s| s.to_string())
|
||||||
.ok_or_else(|| anyhow!("Missing key: {}", key))
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registry of all available message parsers
|
/// Registry of all available message parsers
|
||||||
@@ -244,16 +238,16 @@ mod tests {
|
|||||||
assert_eq!(entry.session_id, "test-session-123");
|
assert_eq!(entry.session_id, "test-session-123");
|
||||||
assert_eq!(entry.app, "XAMARIN_APP");
|
assert_eq!(entry.app, "XAMARIN_APP");
|
||||||
assert_eq!(entry.version, "5.23.0");
|
assert_eq!(entry.version, "5.23.0");
|
||||||
assert_eq!(entry.offline_login_usage, 0);
|
assert_eq!(entry.offline_login_usage, Some(0));
|
||||||
assert!(!entry.is_password_autofill_enabled);
|
assert_eq!(entry.is_password_autofill_enabled, Some(false));
|
||||||
assert_eq!(entry.camera_roll_usage, 0);
|
assert_eq!(entry.camera_roll_usage, Some(0));
|
||||||
assert_eq!(entry.os, "26.2.0");
|
assert_eq!(entry.os, Some("26.2.0".to_string()));
|
||||||
assert_eq!(entry.app_name, "App");
|
assert_eq!(entry.app_name, Some("App".to_string()));
|
||||||
assert!(!entry.touch_id);
|
assert_eq!(entry.touch_id, Some(false));
|
||||||
assert!(entry.is_offline_login_enabled);
|
assert_eq!(entry.is_offline_login_enabled, Some(true));
|
||||||
assert_eq!(entry.model, "iPhone18,1");
|
assert_eq!(entry.model, Some("iPhone18,1".to_string()));
|
||||||
assert_eq!(entry.device, "iOS, Apple");
|
assert_eq!(entry.device, Some("iOS, Apple".to_string()));
|
||||||
assert_eq!(entry.password_autofill_usage, 0);
|
assert_eq!(entry.password_autofill_usage, Some(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,4 +258,83 @@ mod tests {
|
|||||||
let registry = ParserRegistry::new();
|
let registry = ParserRegistry::new();
|
||||||
assert!(registry.parse(line).is_none());
|
assert!(registry.parse(line).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_signature_with_missing_offline_login_usage() {
|
||||||
|
// Line missing offlineLoginUsage field
|
||||||
|
let line = r#"Jan 21 00:00:06 tom013 m1s-kv dt="2026-01-21 00:00:06,154", sessionId=test-123, msg="signature:XAMARIN_APP/5.23.0/ details:isPasswordAutofillEnabled:yes,cameraRollUsage:1,OS:26.2.0,appName:App,touchID:yes,isOfflineLoginEnabled:no,model:iPhone15,3,device:iOS, Apple,passwordAutofillUsage:2 user-agent:test", ex=""#;
|
||||||
|
|
||||||
|
let registry = ParserRegistry::new();
|
||||||
|
let result = registry.parse(line).unwrap().unwrap();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
ParsedMessage::Signature(entry) => {
|
||||||
|
assert_eq!(entry.session_id, "test-123");
|
||||||
|
assert_eq!(entry.app, "XAMARIN_APP");
|
||||||
|
assert_eq!(entry.version, "5.23.0");
|
||||||
|
// Missing field should be None
|
||||||
|
assert_eq!(entry.offline_login_usage, None);
|
||||||
|
// Other fields should be present
|
||||||
|
assert_eq!(entry.is_password_autofill_enabled, Some(true));
|
||||||
|
assert_eq!(entry.camera_roll_usage, Some(1));
|
||||||
|
assert_eq!(entry.os, Some("26.2.0".to_string()));
|
||||||
|
assert_eq!(entry.app_name, Some("App".to_string()));
|
||||||
|
assert_eq!(entry.touch_id, Some(true));
|
||||||
|
assert_eq!(entry.is_offline_login_enabled, Some(false));
|
||||||
|
assert_eq!(entry.model, Some("iPhone15,3".to_string()));
|
||||||
|
assert_eq!(entry.device, Some("iOS, Apple".to_string()));
|
||||||
|
assert_eq!(entry.password_autofill_usage, Some(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_signature_with_missing_password_autofill_usage() {
|
||||||
|
// Line missing passwordAutofillUsage (truncated log line scenario)
|
||||||
|
let line = r#"Jan 21 00:00:06 tom013 m1s-kv dt="2026-01-21 00:00:06,154", sessionId=test-456, msg="signature:XAMARIN_APP/5.23.0/ details:offlineLoginUsage:0,isPasswordAutofillEnabled:no,cameraRollUsage:0,OS:16.0.0,appName:App,touchID:yes,isOfflineLoginEnabled:yes,model:SM-S938B,device:Android", ex=""#;
|
||||||
|
|
||||||
|
let registry = ParserRegistry::new();
|
||||||
|
let result = registry.parse(line).unwrap().unwrap();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
ParsedMessage::Signature(entry) => {
|
||||||
|
assert_eq!(entry.session_id, "test-456");
|
||||||
|
assert_eq!(entry.app, "XAMARIN_APP");
|
||||||
|
// passwordAutofillUsage missing
|
||||||
|
assert_eq!(entry.password_autofill_usage, None);
|
||||||
|
// Other fields present
|
||||||
|
assert_eq!(entry.offline_login_usage, Some(0));
|
||||||
|
assert_eq!(entry.device, Some("Android".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_signature_with_multiple_missing_fields() {
|
||||||
|
// Line missing multiple fields
|
||||||
|
let line = r#"Jan 21 00:00:06 tom013 m1s-kv dt="2026-01-21 00:00:06,154", sessionId=test-789, msg="signature:XAMARIN_APP/5.23.0/ details:OS:26.2.0,appName:App,model:iPhone18,1 user-agent:test", ex=""#;
|
||||||
|
|
||||||
|
let registry = ParserRegistry::new();
|
||||||
|
let result = registry.parse(line).unwrap().unwrap();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
ParsedMessage::Signature(entry) => {
|
||||||
|
assert_eq!(entry.session_id, "test-789");
|
||||||
|
assert_eq!(entry.app, "XAMARIN_APP");
|
||||||
|
assert_eq!(entry.version, "5.23.0");
|
||||||
|
// Missing fields should be None
|
||||||
|
assert_eq!(entry.offline_login_usage, None);
|
||||||
|
assert_eq!(entry.is_password_autofill_enabled, None);
|
||||||
|
assert_eq!(entry.camera_roll_usage, None);
|
||||||
|
assert_eq!(entry.touch_id, None);
|
||||||
|
assert_eq!(entry.is_offline_login_enabled, None);
|
||||||
|
assert_eq!(entry.device, None);
|
||||||
|
assert_eq!(entry.password_autofill_usage, None);
|
||||||
|
// Present fields should have values
|
||||||
|
assert_eq!(entry.os, Some("26.2.0".to_string()));
|
||||||
|
assert_eq!(entry.app_name, Some("App".to_string()));
|
||||||
|
assert_eq!(entry.model, Some("iPhone18,1".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user