use std::fmt;
use serde::{Deserialize, Deserializer, Serialize, de};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Address {
pub space: String,
#[serde(with = "offset_serde")]
pub offset: u64,
pub text: String,
}
impl Address {
pub fn as_str(&self) -> &str {
&self.text
}
}
impl fmt::Display for Address {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.text)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AddressRange {
pub start: Address,
pub end: Address,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SymbolInfo {
pub name: String,
pub address: Address,
pub kind: SymbolKind,
pub source: SymbolSource,
pub namespace: String,
pub primary: bool,
pub external: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SymbolKind {
Function,
Label,
Namespace,
Class,
Library,
Parameter,
LocalVariable,
GlobalVariable,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SymbolSource {
UserDefined,
Imported,
Analysis,
Default,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReferenceInfo {
pub from: Address,
pub to: Address,
pub reference_type: String,
pub operand_index: i32,
pub primary: bool,
pub external: bool,
pub call: bool,
pub data: bool,
pub read: bool,
pub write: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemoryBlockInfo {
pub name: String,
pub range: AddressRange,
pub size: u64,
pub initialized: bool,
pub read: bool,
pub write: bool,
pub execute: bool,
pub volatile: bool,
pub overlay: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DecompileResult {
pub status: DecompileStatus,
#[serde(default)]
pub error_message: Option<String>,
#[serde(default)]
pub c: Option<String>,
#[serde(default)]
pub signature: Option<String>,
#[serde(default)]
pub prototype: Option<FunctionPrototype>,
#[serde(default)]
pub pcode: Option<PcodeSummary>,
#[serde(default)]
pub high_pcode: Option<HighPcodeGraph>,
#[serde(default)]
pub data_flow: Option<DataFlowGraph>,
#[serde(default)]
pub parameters: Vec<HighSymbol>,
#[serde(default)]
pub local_symbols: Vec<HighSymbol>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DecompileStatus {
Completed,
Failed,
TimedOut,
Cancelled,
FailedToStart,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FunctionPrototype {
pub return_type: String,
#[serde(default)]
pub return_type_id: Option<String>,
#[serde(default)]
pub calling_convention: Option<String>,
pub parameter_count: u64,
pub varargs: bool,
pub inline: bool,
pub no_return: bool,
pub has_this_pointer: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PcodeSummary {
pub op_count: u64,
#[serde(default)]
pub op_counts: Vec<NamedCount>,
pub basic_block_count: u64,
pub varnode_count: u64,
#[serde(default)]
pub varnode_space_counts: Vec<NamedCount>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HighPcodeGraph {
#[serde(default)]
pub blocks: Vec<HighPcodeBlock>,
#[serde(default)]
pub edges: Vec<ControlFlowEdgeInfo>,
#[serde(default)]
pub ops: Vec<PcodeOpInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HighPcodeBlock {
pub id: String,
pub index: i32,
pub block_type: String,
#[serde(default)]
pub range: Option<AddressRange>,
#[serde(default)]
pub op_sequence_numbers: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DataFlowGraph {
#[serde(default)]
pub ops: Vec<DataFlowOp>,
#[serde(default)]
pub varnodes: Vec<DataFlowVarnode>,
#[serde(default)]
pub variables: Vec<DataFlowVariable>,
#[serde(default)]
pub edges: Vec<DataFlowEdge>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DataFlowOp {
pub id: String,
pub sequence_number: String,
pub mnemonic: String,
pub opcode: i32,
#[serde(default)]
pub block_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DataFlowVarnode {
pub id: String,
pub kind: String,
#[serde(default)]
pub variable_id: Option<String>,
pub varnode: VarnodeInfo,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DataFlowVariable {
pub id: String,
pub name: String,
pub data_type: String,
#[serde(default)]
pub type_id: Option<String>,
pub size: u64,
pub storage: String,
pub kind: String,
#[serde(default)]
pub symbol_id: Option<i64>,
#[serde(default)]
pub symbol_address: Option<Address>,
#[serde(default)]
pub symbol_type: Option<String>,
#[serde(default)]
pub namespace: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DataFlowEdge {
pub source_id: String,
pub destination_id: String,
pub kind: String,
#[serde(default)]
pub slot: Option<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProgramCallGraph {
#[serde(default)]
pub nodes: Vec<CallGraphNode>,
#[serde(default)]
pub edges: Vec<CallGraphEdge>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CallGraphNode {
pub id: String,
pub kind: String,
pub name: String,
#[serde(default)]
pub address: Option<Address>,
#[serde(default)]
pub namespace: Option<String>,
#[serde(default)]
pub library: Option<String>,
#[serde(default)]
pub function_id: Option<String>,
#[serde(default)]
pub symbol_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CallGraphEdge {
pub id: String,
pub source_id: String,
pub destination_id: String,
pub callsite: Address,
pub reference_type: String,
pub operand_index: i32,
pub computed: bool,
pub thunk_resolved: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProgramMetadata {
#[serde(default)]
pub symbols: Vec<ProgramSymbol>,
#[serde(default)]
pub functions: Vec<ProgramFunction>,
#[serde(default)]
pub types: Vec<ProgramType>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProgramSymbol {
pub id: String,
pub name: String,
pub kind: String,
pub source: String,
pub address: Address,
pub namespace: String,
pub primary: bool,
pub external: bool,
#[serde(default)]
pub type_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProgramFunction {
pub id: String,
pub name: String,
pub entry: Address,
pub namespace: String,
pub external: bool,
pub thunk: bool,
#[serde(default)]
pub thunked_function_id: Option<String>,
#[serde(default)]
pub symbol_id: Option<String>,
pub signature: ProgramFunctionSignature,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProgramFunctionSignature {
pub display: String,
pub calling_convention: String,
#[serde(default)]
pub return_type_id: Option<String>,
#[serde(default)]
pub parameters: Vec<ProgramParameter>,
pub varargs: bool,
pub no_return: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProgramParameter {
pub ordinal: i32,
pub name: String,
#[serde(default)]
pub type_id: Option<String>,
pub storage: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ProgramType {
pub id: String,
pub name: String,
pub display_name: String,
pub size: i64,
pub alignment: i32,
#[serde(default)]
pub category_path: Option<String>,
#[serde(flatten)]
pub details: ProgramTypeDetails,
}
impl<'de> Deserialize<'de> for ProgramType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = serde_json::Map::<String, serde_json::Value>::deserialize(deserializer)?;
validate_program_type_wire_fields(&value).map_err(de::Error::custom)?;
let wire = ProgramTypeWire::deserialize(serde_json::Value::Object(value))
.map_err(de::Error::custom)?;
Ok(Self {
id: wire.id,
name: wire.name,
display_name: wire.display_name,
size: wire.size,
alignment: wire.alignment,
category_path: wire.category_path,
details: wire.details,
})
}
}
#[derive(Deserialize)]
struct ProgramTypeWire {
id: String,
name: String,
display_name: String,
size: i64,
alignment: i32,
#[serde(default)]
category_path: Option<String>,
#[serde(flatten)]
details: ProgramTypeDetails,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum ProgramTypeDetails {
Builtin,
Unknown,
Pointer {
#[serde(default)]
pointee_type_id: Option<String>,
},
Array {
#[serde(default)]
element_type_id: Option<String>,
element_count: i32,
element_size: i32,
},
Structure {
#[serde(default)]
components: Vec<ProgramTypeComponent>,
},
Union {
#[serde(default)]
components: Vec<ProgramTypeComponent>,
},
Enum {
signed: bool,
#[serde(default)]
values: Vec<ProgramEnumValue>,
},
Typedef {
#[serde(default)]
base_type_id: Option<String>,
},
FunctionDefinition {
signature: ProgramFunctionSignature,
},
Bitfield {
#[serde(default)]
base_type_id: Option<String>,
bit_size: i32,
bit_offset: i32,
storage_size: i32,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProgramTypeComponent {
pub ordinal: i32,
pub name: String,
pub offset: i32,
pub length: i32,
#[serde(default)]
pub type_id: Option<String>,
#[serde(default)]
pub bit_size: Option<i32>,
#[serde(default)]
pub bit_offset: Option<i32>,
#[serde(default)]
pub comment: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProgramEnumValue {
pub name: String,
pub value: String,
#[serde(default)]
pub comment: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct NamedCount {
pub name: String,
pub count: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HighSymbol {
pub name: String,
pub data_type: String,
#[serde(default)]
pub type_id: Option<String>,
pub size: u64,
pub storage: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct InstructionInfo {
pub address: Address,
pub mnemonic: String,
#[serde(default)]
pub pcode: Vec<PcodeOpInfo>,
#[serde(default)]
pub operands: Vec<OperandInfo>,
pub length: u64,
#[serde(default)]
pub bytes: Vec<u8>,
#[serde(default)]
pub fallthrough: Option<Address>,
pub flow_type: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OperandInfo {
pub index: i32,
pub text: String,
#[serde(default)]
pub objects: Vec<String>,
#[serde(default)]
pub references: Vec<ReferenceInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DataInfo {
pub address: Address,
pub range: AddressRange,
pub data_type: String,
pub length: u64,
#[serde(default)]
pub value: Option<String>,
pub display: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ControlFlowGraph {
#[serde(default)]
pub blocks: Vec<BasicBlockInfo>,
#[serde(default)]
pub edges: Vec<ControlFlowEdgeInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BasicBlockInfo {
pub id: String,
pub name: String,
pub range: AddressRange,
pub flow_type: String,
#[serde(default)]
pub instruction_addresses: Vec<Address>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ControlFlowEdgeInfo {
pub source_block_id: String,
pub destination_block_id: String,
pub flow_type: String,
#[serde(default)]
pub source_address: Option<Address>,
#[serde(default)]
pub destination_address: Option<Address>,
#[serde(default)]
pub reference_address: Option<Address>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PcodeOpInfo {
pub sequence_number: String,
pub sequence_target: Address,
pub sequence_time: i32,
pub sequence_order: i32,
pub mnemonic: String,
pub opcode: i32,
#[serde(default)]
pub output: Option<VarnodeInfo>,
#[serde(default)]
pub inputs: Vec<VarnodeInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VarnodeInfo {
pub space: String,
#[serde(with = "offset_serde")]
pub offset: u64,
pub size: u64,
pub text: String,
#[serde(default)]
pub address: Option<Address>,
pub constant: bool,
pub register: bool,
pub unique: bool,
pub address_tied: bool,
pub input: bool,
pub persistent: bool,
}
fn validate_program_type_wire_fields(
value: &serde_json::Map<String, serde_json::Value>,
) -> Result<(), String> {
let kind = value
.get("kind")
.and_then(serde_json::Value::as_str)
.ok_or_else(|| "types.kind must be present and a string".to_string())?;
for key in value.keys() {
if !program_type_field_allowed(kind, key.as_str()) {
return Err(format!("unknown field `{key}` in program type `{kind}`"));
}
}
Ok(())
}
fn program_type_field_allowed(kind: &str, field: &str) -> bool {
if matches!(
field,
"id" | "name" | "display_name" | "kind" | "size" | "alignment" | "category_path"
) {
return true;
}
match kind {
"builtin" | "unknown" => false,
"pointer" => field == "pointee_type_id",
"array" => matches!(field, "element_type_id" | "element_count" | "element_size"),
"structure" | "union" => field == "components",
"enum" => matches!(field, "signed" | "values"),
"typedef" => field == "base_type_id",
"function_definition" => field == "signature",
"bitfield" => matches!(
field,
"base_type_id" | "bit_size" | "bit_offset" | "storage_size"
),
_ => true,
}
}
mod offset_serde {
use std::fmt;
use serde::{
Deserializer, Serializer,
de::{self, Visitor},
};
pub fn serialize<S>(offset: &u64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u64(*offset)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(OffsetVisitor)
}
struct OffsetVisitor;
impl<'de> Visitor<'de> for OffsetVisitor {
type Value = u64;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an unsigned integer or decimal string")
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
Ok(value)
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
u64::try_from(value).map_err(|_| E::custom("offset must not be negative"))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse::<u64>().map_err(E::custom)
}
}
}
#[cfg(test)]
mod tests {
use super::Address;
#[test]
fn address_offset_accepts_decimal_string() {
let address: Address = serde_json::from_str(
r#"{"space":"ram","offset":"18446744073709551615","text":"ram:ffff"}"#,
)
.expect("address parses");
assert_eq!(address.offset, u64::MAX);
assert_eq!(address.to_string(), "ram:ffff");
}
}