Merge pull request #11 from starsong-consulting/feat/struct-management
feat: add struct data type management API
This commit is contained in:
commit
8268e55a08
@ -404,6 +404,201 @@ Provides access to string data in the binary.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 6.2 Structs
|
||||||
|
|
||||||
|
Provides functionality for creating and managing struct (composite) data types.
|
||||||
|
|
||||||
|
- **`GET /structs`**: List all struct data types in the program. Supports pagination and filtering.
|
||||||
|
- Query Parameters:
|
||||||
|
- `?offset=[int]`: Number of structs to skip (default: 0).
|
||||||
|
- `?limit=[int]`: Maximum number of structs to return (default: 100).
|
||||||
|
- `?category=[string]`: Filter by category path (e.g. "/winapi").
|
||||||
|
```json
|
||||||
|
// Example Response
|
||||||
|
"result": [
|
||||||
|
{
|
||||||
|
"name": "MyStruct",
|
||||||
|
"path": "/custom/MyStruct",
|
||||||
|
"size": 16,
|
||||||
|
"numFields": 4,
|
||||||
|
"category": "/custom",
|
||||||
|
"description": "Custom data structure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FileHeader",
|
||||||
|
"path": "/FileHeader",
|
||||||
|
"size": 32,
|
||||||
|
"numFields": 8,
|
||||||
|
"category": "/",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_links": {
|
||||||
|
"self": { "href": "/structs?offset=0&limit=100" },
|
||||||
|
"program": { "href": "/program" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`GET /structs?name={struct_name}`**: Get detailed information about a specific struct including all fields.
|
||||||
|
```json
|
||||||
|
// Example Response for GET /structs?name=MyStruct
|
||||||
|
"result": {
|
||||||
|
"name": "MyStruct",
|
||||||
|
"path": "/custom/MyStruct",
|
||||||
|
"size": 16,
|
||||||
|
"category": "/custom",
|
||||||
|
"description": "Custom data structure",
|
||||||
|
"numFields": 4,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 4,
|
||||||
|
"type": "int",
|
||||||
|
"typePath": "/int",
|
||||||
|
"comment": "Unique identifier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flags",
|
||||||
|
"offset": 4,
|
||||||
|
"length": 4,
|
||||||
|
"type": "dword",
|
||||||
|
"typePath": "/dword",
|
||||||
|
"comment": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "data_ptr",
|
||||||
|
"offset": 8,
|
||||||
|
"length": 4,
|
||||||
|
"type": "pointer",
|
||||||
|
"typePath": "/pointer",
|
||||||
|
"comment": "Pointer to data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "size",
|
||||||
|
"offset": 12,
|
||||||
|
"length": 4,
|
||||||
|
"type": "uint",
|
||||||
|
"typePath": "/uint",
|
||||||
|
"comment": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_links": {
|
||||||
|
"self": { "href": "/structs?name=MyStruct" },
|
||||||
|
"structs": { "href": "/structs" },
|
||||||
|
"program": { "href": "/program" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`POST /structs/create`**: Create a new struct data type.
|
||||||
|
- Request Payload:
|
||||||
|
- `name`: Name for the new struct (required).
|
||||||
|
- `category`: Category path (optional, defaults to root).
|
||||||
|
- `description`: Description for the struct (optional).
|
||||||
|
```json
|
||||||
|
// Example Request Payload
|
||||||
|
{
|
||||||
|
"name": "NetworkPacket",
|
||||||
|
"category": "/network",
|
||||||
|
"description": "Network packet structure"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example Response
|
||||||
|
"result": {
|
||||||
|
"name": "NetworkPacket",
|
||||||
|
"path": "/network/NetworkPacket",
|
||||||
|
"category": "/network",
|
||||||
|
"size": 0,
|
||||||
|
"message": "Struct created successfully"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`POST /structs/addfield`**: Add a field to an existing struct.
|
||||||
|
- Request Payload:
|
||||||
|
- `struct`: Name of the struct to modify (required).
|
||||||
|
- `fieldName`: Name for the new field (required).
|
||||||
|
- `fieldType`: Data type for the field (required, e.g. "int", "char", "pointer").
|
||||||
|
- `offset`: Specific offset to insert field (optional, appends to end if not specified).
|
||||||
|
- `comment`: Comment for the field (optional).
|
||||||
|
```json
|
||||||
|
// Example Request Payload
|
||||||
|
{
|
||||||
|
"struct": "NetworkPacket",
|
||||||
|
"fieldName": "header",
|
||||||
|
"fieldType": "dword",
|
||||||
|
"comment": "Packet header"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example Response
|
||||||
|
"result": {
|
||||||
|
"struct": "NetworkPacket",
|
||||||
|
"fieldName": "header",
|
||||||
|
"fieldType": "dword",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 4,
|
||||||
|
"structSize": 4,
|
||||||
|
"message": "Field added successfully"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`POST /structs/updatefield`**: Update an existing field in a struct (rename, change type, or modify comment).
|
||||||
|
- Request Payload:
|
||||||
|
- `struct`: Name of the struct to modify (required).
|
||||||
|
- `fieldOffset` OR `fieldName`: Identify the field to update (one required).
|
||||||
|
- `newName`: New name for the field (optional).
|
||||||
|
- `newType`: New data type for the field (optional).
|
||||||
|
- `newComment`: New comment for the field (optional).
|
||||||
|
- At least one of `newName`, `newType`, or `newComment` must be provided.
|
||||||
|
```json
|
||||||
|
// Example Request Payload - rename a field
|
||||||
|
{
|
||||||
|
"struct": "NetworkPacket",
|
||||||
|
"fieldName": "header",
|
||||||
|
"newName": "packet_header",
|
||||||
|
"newComment": "Updated packet header field"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example Request Payload - change type by offset
|
||||||
|
{
|
||||||
|
"struct": "NetworkPacket",
|
||||||
|
"fieldOffset": 0,
|
||||||
|
"newType": "qword"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example Response
|
||||||
|
"result": {
|
||||||
|
"struct": "NetworkPacket",
|
||||||
|
"offset": 0,
|
||||||
|
"originalName": "header",
|
||||||
|
"originalType": "dword",
|
||||||
|
"originalComment": "Packet header",
|
||||||
|
"newName": "packet_header",
|
||||||
|
"newType": "dword",
|
||||||
|
"newComment": "Updated packet header field",
|
||||||
|
"length": 4,
|
||||||
|
"message": "Field updated successfully"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`POST /structs/delete`**: Delete a struct data type.
|
||||||
|
- Request Payload:
|
||||||
|
- `name`: Name of the struct to delete (required).
|
||||||
|
```json
|
||||||
|
// Example Request Payload
|
||||||
|
{
|
||||||
|
"name": "NetworkPacket"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example Response
|
||||||
|
"result": {
|
||||||
|
"name": "NetworkPacket",
|
||||||
|
"path": "/network/NetworkPacket",
|
||||||
|
"category": "/network",
|
||||||
|
"message": "Struct deleted successfully"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### 7. Memory Segments
|
### 7. Memory Segments
|
||||||
|
|
||||||
Represents memory blocks/sections defined in the program.
|
Represents memory blocks/sections defined in the program.
|
||||||
|
|||||||
@ -48,6 +48,7 @@ The API is organized into namespaces for different types of operations:
|
|||||||
- instances_* : For managing Ghidra instances
|
- instances_* : For managing Ghidra instances
|
||||||
- functions_* : For working with functions
|
- functions_* : For working with functions
|
||||||
- data_* : For working with data items
|
- data_* : For working with data items
|
||||||
|
- structs_* : For creating and managing struct data types
|
||||||
- memory_* : For memory access
|
- memory_* : For memory access
|
||||||
- xrefs_* : For cross-references
|
- xrefs_* : For cross-references
|
||||||
- analysis_* : For program analysis
|
- analysis_* : For program analysis
|
||||||
@ -1924,6 +1925,234 @@ def data_set_type(address: str, data_type: str, port: int = None) -> dict:
|
|||||||
response = safe_post(port, "data/type", payload)
|
response = safe_post(port, "data/type", payload)
|
||||||
return simplify_response(response)
|
return simplify_response(response)
|
||||||
|
|
||||||
|
# Struct tools
|
||||||
|
@mcp.tool()
|
||||||
|
def structs_list(offset: int = 0, limit: int = 100, category: str = None, port: int = None) -> dict:
|
||||||
|
"""List all struct data types in the program
|
||||||
|
|
||||||
|
Args:
|
||||||
|
offset: Pagination offset (default: 0)
|
||||||
|
limit: Maximum items to return (default: 100)
|
||||||
|
category: Filter by category path (e.g. "/winapi")
|
||||||
|
port: Specific Ghidra instance port (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: List of structs with name, size, and field count
|
||||||
|
"""
|
||||||
|
port = _get_instance_port(port)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"offset": offset,
|
||||||
|
"limit": limit
|
||||||
|
}
|
||||||
|
if category:
|
||||||
|
params["category"] = category
|
||||||
|
|
||||||
|
response = safe_get(port, "structs", params)
|
||||||
|
simplified = simplify_response(response)
|
||||||
|
|
||||||
|
# Ensure we maintain pagination metadata
|
||||||
|
if isinstance(simplified, dict) and "error" not in simplified:
|
||||||
|
simplified.setdefault("size", len(simplified.get("result", [])))
|
||||||
|
simplified.setdefault("offset", offset)
|
||||||
|
simplified.setdefault("limit", limit)
|
||||||
|
|
||||||
|
return simplified
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def structs_get(name: str, port: int = None) -> dict:
|
||||||
|
"""Get detailed information about a specific struct including all fields
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Struct name
|
||||||
|
port: Specific Ghidra instance port (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Struct details including all fields with their names, types, and offsets
|
||||||
|
"""
|
||||||
|
if not name:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": {
|
||||||
|
"code": "MISSING_PARAMETER",
|
||||||
|
"message": "Struct name parameter is required"
|
||||||
|
},
|
||||||
|
"timestamp": int(time.time() * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
port = _get_instance_port(port)
|
||||||
|
|
||||||
|
params = {"name": name}
|
||||||
|
response = safe_get(port, "structs", params)
|
||||||
|
return simplify_response(response)
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def structs_create(name: str, category: str = None, description: str = None, port: int = None) -> dict:
|
||||||
|
"""Create a new struct data type
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name for the new struct
|
||||||
|
category: Category path for the struct (e.g. "/custom")
|
||||||
|
description: Optional description for the struct
|
||||||
|
port: Specific Ghidra instance port (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Created struct information
|
||||||
|
"""
|
||||||
|
if not name:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": {
|
||||||
|
"code": "MISSING_PARAMETER",
|
||||||
|
"message": "Struct name parameter is required"
|
||||||
|
},
|
||||||
|
"timestamp": int(time.time() * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
port = _get_instance_port(port)
|
||||||
|
|
||||||
|
payload = {"name": name}
|
||||||
|
if category:
|
||||||
|
payload["category"] = category
|
||||||
|
if description:
|
||||||
|
payload["description"] = description
|
||||||
|
|
||||||
|
response = safe_post(port, "structs/create", payload)
|
||||||
|
return simplify_response(response)
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def structs_add_field(struct_name: str, field_name: str, field_type: str,
|
||||||
|
offset: int = None, comment: str = None, port: int = None) -> dict:
|
||||||
|
"""Add a field to an existing struct
|
||||||
|
|
||||||
|
Args:
|
||||||
|
struct_name: Name of the struct to modify
|
||||||
|
field_name: Name for the new field
|
||||||
|
field_type: Data type for the field (e.g. "int", "char", "pointer")
|
||||||
|
offset: Specific offset to insert field (optional, appends to end if not specified)
|
||||||
|
comment: Optional comment for the field
|
||||||
|
port: Specific Ghidra instance port (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Operation result with updated struct size and field information
|
||||||
|
"""
|
||||||
|
if not struct_name or not field_name or not field_type:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": {
|
||||||
|
"code": "MISSING_PARAMETER",
|
||||||
|
"message": "struct_name, field_name, and field_type parameters are required"
|
||||||
|
},
|
||||||
|
"timestamp": int(time.time() * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
port = _get_instance_port(port)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"struct": struct_name,
|
||||||
|
"fieldName": field_name,
|
||||||
|
"fieldType": field_type
|
||||||
|
}
|
||||||
|
if offset is not None:
|
||||||
|
payload["offset"] = offset
|
||||||
|
if comment:
|
||||||
|
payload["comment"] = comment
|
||||||
|
|
||||||
|
response = safe_post(port, "structs/addfield", payload)
|
||||||
|
return simplify_response(response)
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def structs_update_field(struct_name: str, field_name: str = None, field_offset: int = None,
|
||||||
|
new_name: str = None, new_type: str = None, new_comment: str = None,
|
||||||
|
port: int = None) -> dict:
|
||||||
|
"""Update an existing field in a struct (change name, type, or comment)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
struct_name: Name of the struct to modify
|
||||||
|
field_name: Name of the field to update (use this OR field_offset)
|
||||||
|
field_offset: Offset of the field to update (use this OR field_name)
|
||||||
|
new_name: New name for the field (optional)
|
||||||
|
new_type: New data type for the field (optional, e.g. "int", "pointer")
|
||||||
|
new_comment: New comment for the field (optional)
|
||||||
|
port: Specific Ghidra instance port (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Operation result with old and new field values
|
||||||
|
"""
|
||||||
|
if not struct_name:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": {
|
||||||
|
"code": "MISSING_PARAMETER",
|
||||||
|
"message": "struct_name parameter is required"
|
||||||
|
},
|
||||||
|
"timestamp": int(time.time() * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if not field_name and field_offset is None:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": {
|
||||||
|
"code": "MISSING_PARAMETER",
|
||||||
|
"message": "Either field_name or field_offset must be provided"
|
||||||
|
},
|
||||||
|
"timestamp": int(time.time() * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if not new_name and not new_type and new_comment is None:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": {
|
||||||
|
"code": "MISSING_PARAMETER",
|
||||||
|
"message": "At least one of new_name, new_type, or new_comment must be provided"
|
||||||
|
},
|
||||||
|
"timestamp": int(time.time() * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
port = _get_instance_port(port)
|
||||||
|
|
||||||
|
payload = {"struct": struct_name}
|
||||||
|
if field_name:
|
||||||
|
payload["fieldName"] = field_name
|
||||||
|
if field_offset is not None:
|
||||||
|
payload["fieldOffset"] = field_offset
|
||||||
|
if new_name:
|
||||||
|
payload["newName"] = new_name
|
||||||
|
if new_type:
|
||||||
|
payload["newType"] = new_type
|
||||||
|
if new_comment is not None:
|
||||||
|
payload["newComment"] = new_comment
|
||||||
|
|
||||||
|
response = safe_post(port, "structs/updatefield", payload)
|
||||||
|
return simplify_response(response)
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def structs_delete(name: str, port: int = None) -> dict:
|
||||||
|
"""Delete a struct data type
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the struct to delete
|
||||||
|
port: Specific Ghidra instance port (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Operation result confirming deletion
|
||||||
|
"""
|
||||||
|
if not name:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": {
|
||||||
|
"code": "MISSING_PARAMETER",
|
||||||
|
"message": "Struct name parameter is required"
|
||||||
|
},
|
||||||
|
"timestamp": int(time.time() * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
port = _get_instance_port(port)
|
||||||
|
|
||||||
|
payload = {"name": name}
|
||||||
|
response = safe_post(port, "structs/delete", payload)
|
||||||
|
return simplify_response(response)
|
||||||
|
|
||||||
# Analysis tools
|
# Analysis tools
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def analysis_run(port: int = None, analysis_options: dict = None) -> dict:
|
def analysis_run(port: int = None, analysis_options: dict = None) -> dict:
|
||||||
|
|||||||
@ -139,6 +139,7 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||||||
new SymbolEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
new SymbolEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
||||||
new NamespaceEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
new NamespaceEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
||||||
new DataEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
new DataEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
||||||
|
new StructEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
||||||
new MemoryEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
new MemoryEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
||||||
new XrefsEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
new XrefsEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
||||||
new AnalysisEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
new AnalysisEndpoints(currentProgram, port, tool).registerEndpoints(server);
|
||||||
@ -376,6 +377,7 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||||||
.addLink("data", "/data")
|
.addLink("data", "/data")
|
||||||
.addLink("strings", "/strings")
|
.addLink("strings", "/strings")
|
||||||
.addLink("segments", "/segments")
|
.addLink("segments", "/segments")
|
||||||
|
.addLink("structs", "/structs")
|
||||||
.addLink("memory", "/memory")
|
.addLink("memory", "/memory")
|
||||||
.addLink("xrefs", "/xrefs")
|
.addLink("xrefs", "/xrefs")
|
||||||
.addLink("analysis", "/analysis")
|
.addLink("analysis", "/analysis")
|
||||||
|
|||||||
776
src/main/java/eu/starsong/ghidra/endpoints/StructEndpoints.java
Normal file
776
src/main/java/eu/starsong/ghidra/endpoints/StructEndpoints.java
Normal file
@ -0,0 +1,776 @@
|
|||||||
|
package eu.starsong.ghidra.endpoints;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import eu.starsong.ghidra.api.ResponseBuilder;
|
||||||
|
import eu.starsong.ghidra.util.TransactionHelper;
|
||||||
|
import eu.starsong.ghidra.util.TransactionHelper.TransactionException;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.model.data.*;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoints for managing struct (composite) data types in Ghidra.
|
||||||
|
* Provides REST API for creating, listing, modifying, and deleting structs.
|
||||||
|
*/
|
||||||
|
public class StructEndpoints extends AbstractEndpoint {
|
||||||
|
|
||||||
|
private PluginTool tool;
|
||||||
|
|
||||||
|
public StructEndpoints(Program program, int port) {
|
||||||
|
super(program, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StructEndpoints(Program program, int port, PluginTool tool) {
|
||||||
|
super(program, port);
|
||||||
|
this.tool = tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PluginTool getTool() {
|
||||||
|
return tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerEndpoints(HttpServer server) {
|
||||||
|
server.createContext("/structs", this::handleStructs);
|
||||||
|
server.createContext("/structs/create", exchange -> {
|
||||||
|
try {
|
||||||
|
if ("POST".equals(exchange.getRequestMethod())) {
|
||||||
|
Map<String, String> params = parseJsonPostParams(exchange);
|
||||||
|
handleCreateStruct(exchange, params);
|
||||||
|
} else {
|
||||||
|
sendErrorResponse(exchange, 405, "Method Not Allowed");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error in /structs/create endpoint", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.createContext("/structs/delete", exchange -> {
|
||||||
|
try {
|
||||||
|
if ("POST".equals(exchange.getRequestMethod())) {
|
||||||
|
Map<String, String> params = parseJsonPostParams(exchange);
|
||||||
|
handleDeleteStruct(exchange, params);
|
||||||
|
} else {
|
||||||
|
sendErrorResponse(exchange, 405, "Method Not Allowed");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error in /structs/delete endpoint", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.createContext("/structs/addfield", exchange -> {
|
||||||
|
try {
|
||||||
|
if ("POST".equals(exchange.getRequestMethod())) {
|
||||||
|
Map<String, String> params = parseJsonPostParams(exchange);
|
||||||
|
handleAddField(exchange, params);
|
||||||
|
} else {
|
||||||
|
sendErrorResponse(exchange, 405, "Method Not Allowed");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error in /structs/addfield endpoint", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.createContext("/structs/updatefield", exchange -> {
|
||||||
|
try {
|
||||||
|
if ("POST".equals(exchange.getRequestMethod()) || "PATCH".equals(exchange.getRequestMethod())) {
|
||||||
|
Map<String, String> params = parseJsonPostParams(exchange);
|
||||||
|
handleUpdateField(exchange, params);
|
||||||
|
} else {
|
||||||
|
sendErrorResponse(exchange, 405, "Method Not Allowed");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error in /structs/updatefield endpoint", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle GET /structs - list all structs, or GET /structs?name=X - get specific struct details
|
||||||
|
*/
|
||||||
|
private void handleStructs(HttpExchange exchange) throws IOException {
|
||||||
|
try {
|
||||||
|
if ("GET".equals(exchange.getRequestMethod())) {
|
||||||
|
Map<String, String> qparams = parseQueryParams(exchange);
|
||||||
|
String structName = qparams.get("name");
|
||||||
|
|
||||||
|
if (structName != null && !structName.isEmpty()) {
|
||||||
|
handleGetStruct(exchange, structName);
|
||||||
|
} else {
|
||||||
|
handleListStructs(exchange);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendErrorResponse(exchange, 405, "Method Not Allowed");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error in /structs endpoint", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all struct data types in the program
|
||||||
|
*/
|
||||||
|
private void handleListStructs(HttpExchange exchange) throws IOException {
|
||||||
|
try {
|
||||||
|
Map<String, String> qparams = parseQueryParams(exchange);
|
||||||
|
int offset = parseIntOrDefault(qparams.get("offset"), 0);
|
||||||
|
int limit = parseIntOrDefault(qparams.get("limit"), 100);
|
||||||
|
String categoryFilter = qparams.get("category");
|
||||||
|
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) {
|
||||||
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataTypeManager dtm = program.getDataTypeManager();
|
||||||
|
List<Map<String, Object>> structList = new ArrayList<>();
|
||||||
|
|
||||||
|
// Iterate through all data types and filter for structures
|
||||||
|
dtm.getAllDataTypes().forEachRemaining(dataType -> {
|
||||||
|
if (dataType instanceof Structure) {
|
||||||
|
Structure struct = (Structure) dataType;
|
||||||
|
|
||||||
|
// Apply category filter if specified
|
||||||
|
if (categoryFilter != null && !categoryFilter.isEmpty()) {
|
||||||
|
CategoryPath catPath = struct.getCategoryPath();
|
||||||
|
if (!catPath.getPath().contains(categoryFilter)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> structInfo = new HashMap<>();
|
||||||
|
structInfo.put("name", struct.getName());
|
||||||
|
structInfo.put("path", struct.getPathName());
|
||||||
|
structInfo.put("size", struct.getLength());
|
||||||
|
structInfo.put("numFields", struct.getNumComponents());
|
||||||
|
structInfo.put("category", struct.getCategoryPath().getPath());
|
||||||
|
structInfo.put("description", struct.getDescription() != null ? struct.getDescription() : "");
|
||||||
|
|
||||||
|
// Add HATEOAS links
|
||||||
|
Map<String, Object> links = new HashMap<>();
|
||||||
|
Map<String, String> selfLink = new HashMap<>();
|
||||||
|
selfLink.put("href", "/structs?name=" + struct.getName());
|
||||||
|
links.put("self", selfLink);
|
||||||
|
structInfo.put("_links", links);
|
||||||
|
|
||||||
|
structList.add(structInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort by name for consistency
|
||||||
|
structList.sort(Comparator.comparing(s -> (String) s.get("name")));
|
||||||
|
|
||||||
|
// Build response with pagination
|
||||||
|
ResponseBuilder builder = new ResponseBuilder(exchange, port).success(true);
|
||||||
|
List<Map<String, Object>> paginated = applyPagination(structList, offset, limit, builder, "/structs");
|
||||||
|
builder.result(paginated);
|
||||||
|
builder.addLink("program", "/program");
|
||||||
|
|
||||||
|
sendJsonResponse(exchange, builder.build(), 200);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error listing structs", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Error listing structs: " + e.getMessage(), "INTERNAL_ERROR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get details of a specific struct including all fields
|
||||||
|
*/
|
||||||
|
private void handleGetStruct(HttpExchange exchange, String structName) throws IOException {
|
||||||
|
try {
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) {
|
||||||
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataTypeManager dtm = program.getDataTypeManager();
|
||||||
|
|
||||||
|
// Try to find the struct - support both full paths and simple names
|
||||||
|
DataType dataType = null;
|
||||||
|
|
||||||
|
// If it looks like a full path (starts with /), try direct lookup
|
||||||
|
if (structName.startsWith("/")) {
|
||||||
|
dataType = dtm.getDataType(structName);
|
||||||
|
if (dataType == null) {
|
||||||
|
dataType = dtm.findDataType(structName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Search by simple name using the helper method
|
||||||
|
dataType = findStructByName(dtm, structName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataType == null || !(dataType instanceof Structure)) {
|
||||||
|
sendErrorResponse(exchange, 404, "Struct not found: " + structName, "STRUCT_NOT_FOUND");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Structure struct = (Structure) dataType;
|
||||||
|
Map<String, Object> structInfo = buildStructInfo(struct);
|
||||||
|
|
||||||
|
ResponseBuilder builder = new ResponseBuilder(exchange, port)
|
||||||
|
.success(true)
|
||||||
|
.result(structInfo);
|
||||||
|
|
||||||
|
builder.addLink("self", "/structs?name=" + struct.getName());
|
||||||
|
builder.addLink("structs", "/structs");
|
||||||
|
builder.addLink("program", "/program");
|
||||||
|
|
||||||
|
sendJsonResponse(exchange, builder.build(), 200);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error getting struct details", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Error getting struct: " + e.getMessage(), "INTERNAL_ERROR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new struct data type
|
||||||
|
* POST /structs/create
|
||||||
|
* Required params: name
|
||||||
|
* Optional params: category, size, description
|
||||||
|
*/
|
||||||
|
private void handleCreateStruct(HttpExchange exchange, Map<String, String> params) throws IOException {
|
||||||
|
try {
|
||||||
|
String structName = params.get("name");
|
||||||
|
String category = params.get("category");
|
||||||
|
String sizeStr = params.get("size");
|
||||||
|
String description = params.get("description");
|
||||||
|
|
||||||
|
if (structName == null || structName.isEmpty()) {
|
||||||
|
sendErrorResponse(exchange, 400, "Missing required parameter: name", "MISSING_PARAMETERS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) {
|
||||||
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> resultMap = new HashMap<>();
|
||||||
|
resultMap.put("name", structName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
TransactionHelper.executeInTransaction(program, "Create Struct", () -> {
|
||||||
|
DataTypeManager dtm = program.getDataTypeManager();
|
||||||
|
|
||||||
|
// Check if struct already exists
|
||||||
|
DataType existing = dtm.getDataType("/" + structName);
|
||||||
|
if (existing != null) {
|
||||||
|
throw new Exception("Struct already exists: " + structName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine category path
|
||||||
|
CategoryPath catPath;
|
||||||
|
if (category != null && !category.isEmpty()) {
|
||||||
|
catPath = new CategoryPath(category);
|
||||||
|
} else {
|
||||||
|
catPath = CategoryPath.ROOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the structure
|
||||||
|
StructureDataType struct = new StructureDataType(catPath, structName, 0);
|
||||||
|
|
||||||
|
if (description != null && !description.isEmpty()) {
|
||||||
|
struct.setDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to data type manager
|
||||||
|
Structure addedStruct = (Structure) dtm.addDataType(struct, DataTypeConflictHandler.DEFAULT_HANDLER);
|
||||||
|
|
||||||
|
resultMap.put("path", addedStruct.getPathName());
|
||||||
|
resultMap.put("category", addedStruct.getCategoryPath().getPath());
|
||||||
|
resultMap.put("size", addedStruct.getLength());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
resultMap.put("message", "Struct created successfully");
|
||||||
|
|
||||||
|
ResponseBuilder builder = new ResponseBuilder(exchange, port)
|
||||||
|
.success(true)
|
||||||
|
.result(resultMap);
|
||||||
|
|
||||||
|
builder.addLink("self", "/structs?name=" + structName);
|
||||||
|
builder.addLink("structs", "/structs");
|
||||||
|
builder.addLink("program", "/program");
|
||||||
|
|
||||||
|
sendJsonResponse(exchange, builder.build(), 201);
|
||||||
|
} catch (TransactionException e) {
|
||||||
|
Msg.error(this, "Transaction failed: Create Struct", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Failed to create struct: " + e.getMessage(), "TRANSACTION_ERROR");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error creating struct", e);
|
||||||
|
sendErrorResponse(exchange, 400, "Error creating struct: " + e.getMessage(), "INVALID_PARAMETER");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Unexpected error creating struct", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Error creating struct: " + e.getMessage(), "INTERNAL_ERROR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a field to an existing struct
|
||||||
|
* POST /structs/addfield
|
||||||
|
* Required params: struct, fieldName, fieldType
|
||||||
|
* Optional params: offset, comment
|
||||||
|
*/
|
||||||
|
private void handleAddField(HttpExchange exchange, Map<String, String> params) throws IOException {
|
||||||
|
try {
|
||||||
|
String structName = params.get("struct");
|
||||||
|
String fieldName = params.get("fieldName");
|
||||||
|
String fieldType = params.get("fieldType");
|
||||||
|
String offsetStr = params.get("offset");
|
||||||
|
String comment = params.get("comment");
|
||||||
|
|
||||||
|
if (structName == null || structName.isEmpty()) {
|
||||||
|
sendErrorResponse(exchange, 400, "Missing required parameter: struct", "MISSING_PARAMETERS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fieldName == null || fieldName.isEmpty()) {
|
||||||
|
sendErrorResponse(exchange, 400, "Missing required parameter: fieldName", "MISSING_PARAMETERS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fieldType == null || fieldType.isEmpty()) {
|
||||||
|
sendErrorResponse(exchange, 400, "Missing required parameter: fieldType", "MISSING_PARAMETERS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) {
|
||||||
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer offset = null;
|
||||||
|
if (offsetStr != null && !offsetStr.isEmpty()) {
|
||||||
|
try {
|
||||||
|
offset = Integer.parseInt(offsetStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
sendErrorResponse(exchange, 400, "Invalid offset parameter: must be an integer", "INVALID_PARAMETER");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> resultMap = new HashMap<>();
|
||||||
|
resultMap.put("struct", structName);
|
||||||
|
resultMap.put("fieldName", fieldName);
|
||||||
|
resultMap.put("fieldType", fieldType);
|
||||||
|
|
||||||
|
final Integer finalOffset = offset;
|
||||||
|
|
||||||
|
try {
|
||||||
|
TransactionHelper.executeInTransaction(program, "Add Struct Field", () -> {
|
||||||
|
DataTypeManager dtm = program.getDataTypeManager();
|
||||||
|
|
||||||
|
// Find the struct - handle both full paths and simple names
|
||||||
|
DataType dataType = null;
|
||||||
|
if (structName.startsWith("/")) {
|
||||||
|
dataType = dtm.getDataType(structName);
|
||||||
|
if (dataType == null) {
|
||||||
|
dataType = dtm.findDataType(structName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dataType = findStructByName(dtm, structName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataType == null || !(dataType instanceof Structure)) {
|
||||||
|
throw new Exception("Struct not found: " + structName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Structure struct = (Structure) dataType;
|
||||||
|
|
||||||
|
// Find the field type
|
||||||
|
DataType fieldDataType = findDataType(dtm, fieldType);
|
||||||
|
if (fieldDataType == null) {
|
||||||
|
throw new Exception("Field type not found: " + fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the field
|
||||||
|
DataTypeComponent component;
|
||||||
|
if (finalOffset != null) {
|
||||||
|
// Insert at specific offset
|
||||||
|
component = struct.insertAtOffset(finalOffset, fieldDataType,
|
||||||
|
fieldDataType.getLength(), fieldName, comment);
|
||||||
|
} else {
|
||||||
|
// Append to end
|
||||||
|
component = struct.add(fieldDataType, fieldName, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
resultMap.put("offset", component.getOffset());
|
||||||
|
resultMap.put("length", component.getLength());
|
||||||
|
resultMap.put("structSize", struct.getLength());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
resultMap.put("message", "Field added successfully");
|
||||||
|
|
||||||
|
ResponseBuilder builder = new ResponseBuilder(exchange, port)
|
||||||
|
.success(true)
|
||||||
|
.result(resultMap);
|
||||||
|
|
||||||
|
builder.addLink("struct", "/structs?name=" + structName);
|
||||||
|
builder.addLink("structs", "/structs");
|
||||||
|
builder.addLink("program", "/program");
|
||||||
|
|
||||||
|
sendJsonResponse(exchange, builder.build(), 200);
|
||||||
|
} catch (TransactionException e) {
|
||||||
|
Msg.error(this, "Transaction failed: Add Struct Field", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Failed to add field: " + e.getMessage(), "TRANSACTION_ERROR");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error adding field", e);
|
||||||
|
sendErrorResponse(exchange, 400, "Error adding field: " + e.getMessage(), "INVALID_PARAMETER");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Unexpected error adding field", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Error adding field: " + e.getMessage(), "INTERNAL_ERROR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing field in a struct
|
||||||
|
* POST/PATCH /structs/updatefield
|
||||||
|
* Required params: struct, fieldOffset (or fieldName)
|
||||||
|
* Optional params: newName, newType, newComment
|
||||||
|
*/
|
||||||
|
private void handleUpdateField(HttpExchange exchange, Map<String, String> params) throws IOException {
|
||||||
|
try {
|
||||||
|
String structName = params.get("struct");
|
||||||
|
String fieldOffsetStr = params.get("fieldOffset");
|
||||||
|
String fieldName = params.get("fieldName");
|
||||||
|
String newName = params.get("newName");
|
||||||
|
String newType = params.get("newType");
|
||||||
|
String newComment = params.get("newComment");
|
||||||
|
|
||||||
|
if (structName == null || structName.isEmpty()) {
|
||||||
|
sendErrorResponse(exchange, 400, "Missing required parameter: struct", "MISSING_PARAMETERS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must have either fieldOffset or fieldName to identify the field
|
||||||
|
if ((fieldOffsetStr == null || fieldOffsetStr.isEmpty()) && (fieldName == null || fieldName.isEmpty())) {
|
||||||
|
sendErrorResponse(exchange, 400, "Missing required parameter: either fieldOffset or fieldName must be provided", "MISSING_PARAMETERS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must have at least one update parameter
|
||||||
|
if ((newName == null || newName.isEmpty()) &&
|
||||||
|
(newType == null || newType.isEmpty()) &&
|
||||||
|
(newComment == null || newComment.isEmpty())) {
|
||||||
|
sendErrorResponse(exchange, 400, "At least one of newName, newType, or newComment must be provided", "MISSING_PARAMETERS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) {
|
||||||
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer fieldOffset = null;
|
||||||
|
if (fieldOffsetStr != null && !fieldOffsetStr.isEmpty()) {
|
||||||
|
try {
|
||||||
|
fieldOffset = Integer.parseInt(fieldOffsetStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
sendErrorResponse(exchange, 400, "Invalid fieldOffset parameter: must be an integer", "INVALID_PARAMETER");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> resultMap = new HashMap<>();
|
||||||
|
resultMap.put("struct", structName);
|
||||||
|
|
||||||
|
final Integer finalFieldOffset = fieldOffset;
|
||||||
|
final String finalFieldName = fieldName;
|
||||||
|
|
||||||
|
try {
|
||||||
|
TransactionHelper.executeInTransaction(program, "Update Struct Field", () -> {
|
||||||
|
DataTypeManager dtm = program.getDataTypeManager();
|
||||||
|
|
||||||
|
// Find the struct
|
||||||
|
DataType dataType = null;
|
||||||
|
if (structName.startsWith("/")) {
|
||||||
|
dataType = dtm.getDataType(structName);
|
||||||
|
if (dataType == null) {
|
||||||
|
dataType = dtm.findDataType(structName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dataType = findStructByName(dtm, structName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataType == null || !(dataType instanceof Structure)) {
|
||||||
|
throw new Exception("Struct not found: " + structName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Structure struct = (Structure) dataType;
|
||||||
|
|
||||||
|
// Find the field to update
|
||||||
|
DataTypeComponent component = null;
|
||||||
|
if (finalFieldOffset != null) {
|
||||||
|
component = struct.getComponentAt(finalFieldOffset);
|
||||||
|
} else {
|
||||||
|
// Search by field name
|
||||||
|
for (DataTypeComponent comp : struct.getComponents()) {
|
||||||
|
if (finalFieldName.equals(comp.getFieldName())) {
|
||||||
|
component = comp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component == null) {
|
||||||
|
throw new Exception("Field not found in struct: " + (finalFieldOffset != null ? "offset " + finalFieldOffset : finalFieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
int componentOffset = component.getOffset();
|
||||||
|
int componentLength = component.getLength();
|
||||||
|
DataType originalType = component.getDataType();
|
||||||
|
String originalName = component.getFieldName();
|
||||||
|
String originalComment = component.getComment();
|
||||||
|
|
||||||
|
// Store original values
|
||||||
|
resultMap.put("originalName", originalName);
|
||||||
|
resultMap.put("originalType", originalType.getName());
|
||||||
|
resultMap.put("originalComment", originalComment != null ? originalComment : "");
|
||||||
|
resultMap.put("offset", componentOffset);
|
||||||
|
|
||||||
|
// Determine new values
|
||||||
|
String updatedName = (newName != null && !newName.isEmpty()) ? newName : originalName;
|
||||||
|
String updatedComment = (newComment != null) ? newComment : originalComment;
|
||||||
|
DataType updatedType = originalType;
|
||||||
|
|
||||||
|
if (newType != null && !newType.isEmpty()) {
|
||||||
|
updatedType = findDataType(dtm, newType);
|
||||||
|
if (updatedType == null) {
|
||||||
|
throw new Exception("Field type not found: " + newType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the field by replacing it
|
||||||
|
// Ghidra doesn't have a direct "update" - we need to delete and re-add
|
||||||
|
struct.deleteAtOffset(componentOffset);
|
||||||
|
DataTypeComponent newComponent = struct.insertAtOffset(componentOffset, updatedType,
|
||||||
|
updatedType.getLength(),
|
||||||
|
updatedName, updatedComment);
|
||||||
|
|
||||||
|
resultMap.put("newName", newComponent.getFieldName());
|
||||||
|
resultMap.put("newType", newComponent.getDataType().getName());
|
||||||
|
resultMap.put("newComment", newComponent.getComment() != null ? newComponent.getComment() : "");
|
||||||
|
resultMap.put("length", newComponent.getLength());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
resultMap.put("message", "Field updated successfully");
|
||||||
|
|
||||||
|
ResponseBuilder builder = new ResponseBuilder(exchange, port)
|
||||||
|
.success(true)
|
||||||
|
.result(resultMap);
|
||||||
|
|
||||||
|
builder.addLink("struct", "/structs?name=" + structName);
|
||||||
|
builder.addLink("structs", "/structs");
|
||||||
|
builder.addLink("program", "/program");
|
||||||
|
|
||||||
|
sendJsonResponse(exchange, builder.build(), 200);
|
||||||
|
} catch (TransactionException e) {
|
||||||
|
Msg.error(this, "Transaction failed: Update Struct Field", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Failed to update field: " + e.getMessage(), "TRANSACTION_ERROR");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error updating field", e);
|
||||||
|
sendErrorResponse(exchange, 400, "Error updating field: " + e.getMessage(), "INVALID_PARAMETER");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Unexpected error updating field", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Error updating field: " + e.getMessage(), "INTERNAL_ERROR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a struct data type
|
||||||
|
* POST /structs/delete
|
||||||
|
* Required params: name
|
||||||
|
*/
|
||||||
|
private void handleDeleteStruct(HttpExchange exchange, Map<String, String> params) throws IOException {
|
||||||
|
try {
|
||||||
|
String structName = params.get("name");
|
||||||
|
|
||||||
|
if (structName == null || structName.isEmpty()) {
|
||||||
|
sendErrorResponse(exchange, 400, "Missing required parameter: name", "MISSING_PARAMETERS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Program program = getCurrentProgram();
|
||||||
|
if (program == null) {
|
||||||
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> resultMap = new HashMap<>();
|
||||||
|
resultMap.put("name", structName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
TransactionHelper.executeInTransaction(program, "Delete Struct", () -> {
|
||||||
|
DataTypeManager dtm = program.getDataTypeManager();
|
||||||
|
|
||||||
|
// Find the struct - handle both full paths and simple names
|
||||||
|
DataType dataType = null;
|
||||||
|
if (structName.startsWith("/")) {
|
||||||
|
dataType = dtm.getDataType(structName);
|
||||||
|
if (dataType == null) {
|
||||||
|
dataType = dtm.findDataType(structName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dataType = findStructByName(dtm, structName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataType == null) {
|
||||||
|
throw new Exception("Struct not found: " + structName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(dataType instanceof Structure)) {
|
||||||
|
throw new Exception("Data type is not a struct: " + structName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store info before deletion
|
||||||
|
resultMap.put("path", dataType.getPathName());
|
||||||
|
resultMap.put("category", dataType.getCategoryPath().getPath());
|
||||||
|
|
||||||
|
// Remove the struct
|
||||||
|
dtm.remove(dataType, null);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
resultMap.put("message", "Struct deleted successfully");
|
||||||
|
|
||||||
|
ResponseBuilder builder = new ResponseBuilder(exchange, port)
|
||||||
|
.success(true)
|
||||||
|
.result(resultMap);
|
||||||
|
|
||||||
|
builder.addLink("structs", "/structs");
|
||||||
|
builder.addLink("program", "/program");
|
||||||
|
|
||||||
|
sendJsonResponse(exchange, builder.build(), 200);
|
||||||
|
} catch (TransactionException e) {
|
||||||
|
Msg.error(this, "Transaction failed: Delete Struct", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Failed to delete struct: " + e.getMessage(), "TRANSACTION_ERROR");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Error deleting struct", e);
|
||||||
|
sendErrorResponse(exchange, 400, "Error deleting struct: " + e.getMessage(), "INVALID_PARAMETER");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Msg.error(this, "Unexpected error deleting struct", e);
|
||||||
|
sendErrorResponse(exchange, 500, "Error deleting struct: " + e.getMessage(), "INTERNAL_ERROR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a detailed information map for a struct including all fields
|
||||||
|
*/
|
||||||
|
private Map<String, Object> buildStructInfo(Structure struct) {
|
||||||
|
Map<String, Object> structInfo = new HashMap<>();
|
||||||
|
structInfo.put("name", struct.getName());
|
||||||
|
structInfo.put("path", struct.getPathName());
|
||||||
|
structInfo.put("size", struct.getLength());
|
||||||
|
structInfo.put("category", struct.getCategoryPath().getPath());
|
||||||
|
structInfo.put("description", struct.getDescription() != null ? struct.getDescription() : "");
|
||||||
|
structInfo.put("numFields", struct.getNumComponents());
|
||||||
|
|
||||||
|
// Add field details
|
||||||
|
List<Map<String, Object>> fields = new ArrayList<>();
|
||||||
|
for (DataTypeComponent component : struct.getComponents()) {
|
||||||
|
Map<String, Object> fieldInfo = new HashMap<>();
|
||||||
|
fieldInfo.put("name", component.getFieldName() != null ? component.getFieldName() : "");
|
||||||
|
fieldInfo.put("offset", component.getOffset());
|
||||||
|
fieldInfo.put("length", component.getLength());
|
||||||
|
fieldInfo.put("type", component.getDataType().getName());
|
||||||
|
fieldInfo.put("typePath", component.getDataType().getPathName());
|
||||||
|
fieldInfo.put("comment", component.getComment() != null ? component.getComment() : "");
|
||||||
|
fields.add(fieldInfo);
|
||||||
|
}
|
||||||
|
structInfo.put("fields", fields);
|
||||||
|
|
||||||
|
return structInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a struct by name, searching through all data types
|
||||||
|
*/
|
||||||
|
private DataType findStructByName(DataTypeManager dtm, String structName) {
|
||||||
|
final DataType[] result = new DataType[1];
|
||||||
|
|
||||||
|
dtm.getAllDataTypes().forEachRemaining(dt -> {
|
||||||
|
if (dt instanceof Structure && dt.getName().equals(structName)) {
|
||||||
|
if (result[0] == null) {
|
||||||
|
result[0] = dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a data type by name, trying multiple lookup methods
|
||||||
|
*/
|
||||||
|
private DataType findDataType(DataTypeManager dtm, String typeName) {
|
||||||
|
// Try direct lookup with path
|
||||||
|
DataType dataType = dtm.getDataType("/" + typeName);
|
||||||
|
|
||||||
|
// Try without path
|
||||||
|
if (dataType == null) {
|
||||||
|
dataType = dtm.findDataType("/" + typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try built-in primitive types
|
||||||
|
if (dataType == null) {
|
||||||
|
switch(typeName.toLowerCase()) {
|
||||||
|
case "byte":
|
||||||
|
dataType = new ByteDataType();
|
||||||
|
break;
|
||||||
|
case "char":
|
||||||
|
dataType = new CharDataType();
|
||||||
|
break;
|
||||||
|
case "word":
|
||||||
|
dataType = new WordDataType();
|
||||||
|
break;
|
||||||
|
case "dword":
|
||||||
|
dataType = new DWordDataType();
|
||||||
|
break;
|
||||||
|
case "qword":
|
||||||
|
dataType = new QWordDataType();
|
||||||
|
break;
|
||||||
|
case "float":
|
||||||
|
dataType = new FloatDataType();
|
||||||
|
break;
|
||||||
|
case "double":
|
||||||
|
dataType = new DoubleDataType();
|
||||||
|
break;
|
||||||
|
case "int":
|
||||||
|
dataType = new IntegerDataType();
|
||||||
|
break;
|
||||||
|
case "long":
|
||||||
|
dataType = new LongDataType();
|
||||||
|
break;
|
||||||
|
case "pointer":
|
||||||
|
dataType = new PointerDataType();
|
||||||
|
break;
|
||||||
|
case "string":
|
||||||
|
dataType = new StringDataType();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataType;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user