1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
"""Transport-agnostic error hierarchy and the Session model.
These were originally defined in client.py (the ida-pro-mcp HTTP client), but the
idalib worker path (worker_client / domain / app) needs the same exception types
and Session dataclass without dragging in the HTTP transport. They live here so
both backends share one definition; client.py re-exports them for backwards
compatibility with the (deprecated) mcp tooling and the stress tests.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
# --------------------------------------------------------------------------- #
# Exceptions
# --------------------------------------------------------------------------- #
class IDAError(Exception):
"""Base class for all client errors."""
class IDAConnectionError(IDAError):
"""The transport could not be established or was lost."""
class IDATimeoutError(IDAError):
"""A request exceeded its deadline."""
class IDAProtocolError(IDAError):
"""Malformed or unexpected HTTP / JSON-RPC framing."""
class IDARPCError(IDAError):
"""The JSON-RPC envelope carried an ``error`` object."""
def __init__(self, code: int, message: str, data: Any = None):
super().__init__(f"JSON-RPC error {code}: {message}")
self.code = code
self.message = message
self.data = data
class IDAToolError(IDAError):
"""A tool call returned ``result.isError == true`` (a hard failure)."""
def __init__(self, tool: str, message: str):
super().__init__(f"tool {tool!r} failed: {message}")
self.tool = tool
self.message = message
class IDASessionError(IDAError):
"""No IDB session is open, or several are and none was pinned."""
# --------------------------------------------------------------------------- #
# Session model
# --------------------------------------------------------------------------- #
@dataclass(frozen=True)
class Session:
session_id: str
filename: str
input_path: str
is_active: bool = False
is_analyzing: bool = False
@classmethod
def from_dict(cls, d: dict) -> "Session":
return cls(
session_id=d.get("session_id", ""),
filename=d.get("filename", ""),
input_path=d.get("input_path", ""),
is_active=bool(d.get("is_active", False)),
is_analyzing=bool(d.get("is_analyzing", False)),
)
|