
This PR extracts the `SemanticVersion` out of `util` and into its own `SemanticVersion` crate. This allows for making use of `SemanticVersion` without needing to pull in some of the heavier dependencies included in the `util` crate. As part of this the public API for `SemanticVersion` has been tidied up a bit. Release Notes: - N/A
351 lines
11 KiB
Rust
351 lines
11 KiB
Rust
use collections::HashMap;
|
|
|
|
use semantic_version::SemanticVersion;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
|
|
#[derive(Debug)]
|
|
pub struct IpsFile {
|
|
pub header: Header,
|
|
pub body: Body,
|
|
}
|
|
|
|
impl IpsFile {
|
|
pub fn parse(bytes: &[u8]) -> anyhow::Result<IpsFile> {
|
|
let mut split = bytes.splitn(2, |&b| b == b'\n');
|
|
let header_bytes = split
|
|
.next()
|
|
.ok_or_else(|| anyhow::anyhow!("No header found"))?;
|
|
let header: Header = serde_json::from_slice(header_bytes)
|
|
.map_err(|e| anyhow::anyhow!("Failed to parse header: {}", e))?;
|
|
|
|
let body_bytes = split
|
|
.next()
|
|
.ok_or_else(|| anyhow::anyhow!("No body found"))?;
|
|
|
|
let body: Body = serde_json::from_slice(body_bytes)
|
|
.map_err(|e| anyhow::anyhow!("Failed to parse body: {}", e))?;
|
|
Ok(IpsFile { header, body })
|
|
}
|
|
|
|
pub fn faulting_thread(&self) -> Option<&Thread> {
|
|
self.body.threads.get(self.body.faulting_thread? as usize)
|
|
}
|
|
|
|
pub fn app_version(&self) -> Option<SemanticVersion> {
|
|
self.header.app_version.parse().ok()
|
|
}
|
|
|
|
pub fn timestamp(&self) -> anyhow::Result<chrono::DateTime<chrono::FixedOffset>> {
|
|
chrono::DateTime::parse_from_str(&self.header.timestamp, "%Y-%m-%d %H:%M:%S%.f %#z")
|
|
.map_err(|e| anyhow::anyhow!(e))
|
|
}
|
|
|
|
pub fn description(&self, panic: Option<&str>) -> String {
|
|
let mut desc = if self.body.termination.indicator == "Abort trap: 6" {
|
|
match panic {
|
|
Some(panic_message) => format!("Panic `{}`", panic_message),
|
|
None => "Crash `Abort trap: 6` (possible panic)".into(),
|
|
}
|
|
} else if let Some(msg) = &self.body.exception.message {
|
|
format!("Exception `{}`", msg)
|
|
} else {
|
|
format!("Crash `{}`", self.body.termination.indicator)
|
|
};
|
|
if let Some(thread) = self.faulting_thread() {
|
|
if let Some(queue) = thread.queue.as_ref() {
|
|
desc += &format!(
|
|
" on thread {} ({})",
|
|
self.body.faulting_thread.unwrap_or_default(),
|
|
queue
|
|
);
|
|
} else {
|
|
desc += &format!(
|
|
" on thread {} ({})",
|
|
self.body.faulting_thread.unwrap_or_default(),
|
|
thread.name.clone().unwrap_or_default()
|
|
);
|
|
}
|
|
}
|
|
desc
|
|
}
|
|
|
|
pub fn backtrace_summary(&self) -> String {
|
|
if let Some(thread) = self.faulting_thread() {
|
|
let mut frames = thread
|
|
.frames
|
|
.iter()
|
|
.filter_map(|frame| {
|
|
if let Some(name) = &frame.symbol {
|
|
if self.is_ignorable_frame(name) {
|
|
return None;
|
|
}
|
|
Some(format!("{:#}", rustc_demangle::demangle(name)))
|
|
} else if let Some(image) = self.body.used_images.get(frame.image_index) {
|
|
Some(image.name.clone().unwrap_or("<unknown-image>".into()))
|
|
} else {
|
|
Some("<unknown>".into())
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let total = frames.len();
|
|
if total > 21 {
|
|
frames = frames.into_iter().take(20).collect();
|
|
frames.push(format!(" and {} more...", total - 20))
|
|
}
|
|
frames.join("\n")
|
|
} else {
|
|
"<no backtrace available>".into()
|
|
}
|
|
}
|
|
|
|
fn is_ignorable_frame(&self, symbol: &String) -> bool {
|
|
[
|
|
"pthread_kill",
|
|
"panic",
|
|
"backtrace",
|
|
"rust_begin_unwind",
|
|
"abort",
|
|
]
|
|
.iter()
|
|
.any(|s| symbol.contains(s))
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(default)]
|
|
pub struct Header {
|
|
pub app_name: String,
|
|
pub timestamp: String,
|
|
pub app_version: String,
|
|
pub slice_uuid: String,
|
|
pub build_version: String,
|
|
pub platform: i64,
|
|
#[serde(rename = "bundleID", default)]
|
|
pub bundle_id: String,
|
|
pub share_with_app_devs: i64,
|
|
pub is_first_party: i64,
|
|
pub bug_type: String,
|
|
pub os_version: String,
|
|
pub roots_installed: i64,
|
|
pub name: String,
|
|
pub incident_id: String,
|
|
}
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct Body {
|
|
pub uptime: i64,
|
|
pub proc_role: String,
|
|
pub version: i64,
|
|
#[serde(rename = "userID")]
|
|
pub user_id: i64,
|
|
pub deploy_version: i64,
|
|
pub model_code: String,
|
|
#[serde(rename = "coalitionID")]
|
|
pub coalition_id: i64,
|
|
pub os_version: OsVersion,
|
|
pub capture_time: String,
|
|
pub code_signing_monitor: i64,
|
|
pub incident: String,
|
|
pub pid: i64,
|
|
pub translated: bool,
|
|
pub cpu_type: String,
|
|
#[serde(rename = "roots_installed")]
|
|
pub roots_installed: i64,
|
|
#[serde(rename = "bug_type")]
|
|
pub bug_type: String,
|
|
pub proc_launch: String,
|
|
pub proc_start_abs_time: i64,
|
|
pub proc_exit_abs_time: i64,
|
|
pub proc_name: String,
|
|
pub proc_path: String,
|
|
pub bundle_info: BundleInfo,
|
|
pub store_info: StoreInfo,
|
|
pub parent_proc: String,
|
|
pub parent_pid: i64,
|
|
pub coalition_name: String,
|
|
pub crash_reporter_key: String,
|
|
#[serde(rename = "codeSigningID")]
|
|
pub code_signing_id: String,
|
|
#[serde(rename = "codeSigningTeamID")]
|
|
pub code_signing_team_id: String,
|
|
pub code_signing_flags: i64,
|
|
pub code_signing_validation_category: i64,
|
|
pub code_signing_trust_level: i64,
|
|
pub instruction_byte_stream: InstructionByteStream,
|
|
pub sip: String,
|
|
pub exception: Exception,
|
|
pub termination: Termination,
|
|
pub asi: Asi,
|
|
pub ext_mods: ExtMods,
|
|
pub faulting_thread: Option<i64>,
|
|
pub threads: Vec<Thread>,
|
|
pub used_images: Vec<UsedImage>,
|
|
pub shared_cache: SharedCache,
|
|
pub vm_summary: String,
|
|
pub legacy_info: LegacyInfo,
|
|
pub log_writing_signature: String,
|
|
pub trial_info: TrialInfo,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct OsVersion {
|
|
pub train: String,
|
|
pub build: String,
|
|
pub release_type: String,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct BundleInfo {
|
|
#[serde(rename = "CFBundleShortVersionString")]
|
|
pub cfbundle_short_version_string: String,
|
|
#[serde(rename = "CFBundleVersion")]
|
|
pub cfbundle_version: String,
|
|
#[serde(rename = "CFBundleIdentifier")]
|
|
pub cfbundle_identifier: String,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct StoreInfo {
|
|
pub device_identifier_for_vendor: String,
|
|
pub third_party: bool,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct InstructionByteStream {
|
|
#[serde(rename = "beforePC")]
|
|
pub before_pc: String,
|
|
#[serde(rename = "atPC")]
|
|
pub at_pc: String,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct Exception {
|
|
pub codes: String,
|
|
pub raw_codes: Vec<i64>,
|
|
#[serde(rename = "type")]
|
|
pub type_field: String,
|
|
pub subtype: Option<String>,
|
|
pub signal: String,
|
|
pub port: Option<i64>,
|
|
pub guard_id: Option<i64>,
|
|
pub message: Option<String>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct Termination {
|
|
pub flags: i64,
|
|
pub code: i64,
|
|
pub namespace: String,
|
|
pub indicator: String,
|
|
pub by_proc: String,
|
|
pub by_pid: i64,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct Asi {
|
|
#[serde(rename = "libsystem_c.dylib")]
|
|
pub libsystem_c_dylib: Vec<String>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct ExtMods {
|
|
pub caller: ExtMod,
|
|
pub system: ExtMod,
|
|
pub targeted: ExtMod,
|
|
pub warnings: i64,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct ExtMod {
|
|
#[serde(rename = "thread_create")]
|
|
pub thread_create: i64,
|
|
#[serde(rename = "thread_set_state")]
|
|
pub thread_set_state: i64,
|
|
#[serde(rename = "task_for_pid")]
|
|
pub task_for_pid: i64,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct Thread {
|
|
pub thread_state: HashMap<String, Value>,
|
|
pub id: i64,
|
|
pub triggered: Option<bool>,
|
|
pub name: Option<String>,
|
|
pub queue: Option<String>,
|
|
pub frames: Vec<Frame>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct Frame {
|
|
pub image_offset: i64,
|
|
pub symbol: Option<String>,
|
|
pub symbol_location: Option<i64>,
|
|
pub image_index: usize,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct UsedImage {
|
|
pub source: String,
|
|
pub arch: Option<String>,
|
|
pub base: i64,
|
|
#[serde(rename = "CFBundleShortVersionString")]
|
|
pub cfbundle_short_version_string: Option<String>,
|
|
#[serde(rename = "CFBundleIdentifier")]
|
|
pub cfbundle_identifier: Option<String>,
|
|
pub size: i64,
|
|
pub uuid: String,
|
|
pub path: Option<String>,
|
|
pub name: Option<String>,
|
|
#[serde(rename = "CFBundleVersion")]
|
|
pub cfbundle_version: Option<String>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct SharedCache {
|
|
pub base: i64,
|
|
pub size: i64,
|
|
pub uuid: String,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct LegacyInfo {
|
|
pub thread_triggered: ThreadTriggered,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct ThreadTriggered {
|
|
pub name: String,
|
|
pub queue: String,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct TrialInfo {
|
|
pub rollouts: Vec<Rollout>,
|
|
pub experiments: Vec<Value>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", default)]
|
|
pub struct Rollout {
|
|
pub rollout_id: String,
|
|
pub factor_pack_ids: HashMap<String, Value>,
|
|
pub deployment_id: i64,
|
|
}
|