Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions frontend/backend/ofrak_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@
from ofrak_type.error import NotFoundError
from ofrak_type.range import Range

from ofrak import OFRAKContext, OFRAK
from ofrak.core import File
from ofrak import (
OFRAKContext,
OFRAK,
ResourceFilter,
ResourceAttributeRangeFilter,
ResourceAttributeValueFilter,
ResourceSort,
)
from ofrak.core import File, Addressable
from ofrak.core import (
GenericBinary,
AddCommentModifier,
Expand Down Expand Up @@ -131,6 +138,7 @@ def __init__(
web.post("/{resource_id}/find_and_replace", self.find_and_replace),
web.post("/{resource_id}/add_comment", self.add_comment),
web.post("/{resource_id}/delete_comment", self.delete_comment),
web.post("/{resource_id}/search_for_vaddr", self.search_for_vaddr),
]
)

Expand Down Expand Up @@ -330,6 +338,28 @@ async def delete_comment(self, request: Request) -> Response:
)
return web.json_response(await self._serialize_component_result(result))

@exceptions_to_http(SerializedError)
async def search_for_vaddr(self, request: Request) -> Response:
resource = await self._get_resource_for_request(request)
vaddr_start, vaddr_end = self._serializer.from_pjson(
await request.json(), Tuple[int, Optional[int]]
)
try:
if vaddr_end is not None:
vaddr_filter = ResourceAttributeRangeFilter(
Addressable.VirtualAddress, vaddr_start, vaddr_end
)
else:
vaddr_filter = ResourceAttributeValueFilter(Addressable.VirtualAddress, vaddr_start)
matching_resources = await resource.get_descendants(
r_filter=ResourceFilter(attribute_filters=(vaddr_filter,)),
r_sort=ResourceSort(Addressable.VirtualAddress),
)
return web.json_response(list(map(self._serialize_resource, matching_resources)))

except NotFoundError:
return web.json_response([])

async def _get_resource_by_id(self, resource_id: bytes, job_id: bytes) -> Resource:
resource = await self._ofrak_context.resource_factory.create(
job_id,
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/ResourceTreeToolbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import Toolbar from "./Toolbar.svelte";

import { selectedResource, selected } from "./stores.js";
import SearchView from "./SearchView.svelte";

export let resourceNodeDataMap, modifierView;
$: rootResource = $selectedResource;
Expand Down Expand Up @@ -187,6 +188,14 @@
modifierView = CommentView;
},
},

{
text: "Search",
iconUrl: "/icons/identify.svg",
Comment thread
EdwardLarson marked this conversation as resolved.
onclick: async (e) => {
modifierView = SearchView;
},
},
];
}
</script>
Expand Down
215 changes: 215 additions & 0 deletions frontend/src/SearchView.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
<style>
button {
padding-top: 0.5em;
padding-bottom: 0.5em;
padding-left: 1em;
padding-right: 1em;
}

button:hover,
button:focus {
outline: none;
box-shadow: inset 1px 1px 0 var(--main-fg-color),
inset -1px -1px 0 var(--main-fg-color);
}

button:active {
box-shadow: inset 2px 2px 0 var(--main-fg-color),
inset -2px -2px 0 var(--main-fg-color);
Comment thread
EdwardLarson marked this conversation as resolved.
}

.container {
min-height: 100%;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
align-items: stretch;
align-content: center;
}

.inputs {
flex-grow: 1;
}

.inputs *:first-child {
margin-top: 0;
}

.actions {
margin-top: 2em;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-evenly;
align-items: flex-start;
align-content: flex-start;
}

input {
background: inherit;
color: inherit;
border: none;
border-bottom: 1px solid white;
flex-grow: 1;
margin-left: 1ch;
}

input:focus {
outline: none;
box-shadow: inset 0 -1px 0 var(--main-fg-color);
}

/* Adapted from: https://moderncss.dev/pure-css-custom-checkbox-style/ */
input[type="checkbox"] {
flex-grow: 0;
margin-left: 1ch;
}

label {
margin-bottom: 1em;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-evenly;
align-items: baseline;
align-content: center;
white-space: nowrap;
}

.row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-evenly;
align-items: baseline;
align-content: center;
white-space: nowrap;
}

.error {
margin-top: 2em;
}

.treebox {
flex-grow: 1;
padding-left: 1em;
overflow-x: scroll;
white-space: nowrap;
text-align: left;
}
</style>

<script>
import { selected, selectedResource } from "./stores.js";
import { calculator } from "./helpers";
import ResourceTreeNode from "./ResourceTreeNode.svelte";

export let modifierView, resourceNodeDataMap;
let searchInput,
searchRangeStartInput,
searchRangeEndInput,
rangeSearch = false,
results = [],
errorMessage;

const searchTarget = $selectedResource;

function refreshResource() {
// Force hex view refresh with colors
const originalSelected = $selected;
$selected = undefined;
$selected = originalSelected;
}

async function search() {
if (searchTarget) {
try {
if (rangeSearch) {
const searchRangeStartAddress = calculator.calculate(
searchRangeStartInput
),
searchRangeEndAddress = calculator.calculate(searchRangeEndInput);

results = await searchTarget.search_for_vaddr(
searchRangeStartAddress,
searchRangeEndAddress
);
} else {
const startAddress = calculator.calculate(searchInput);

results = await searchTarget.search_for_vaddr(startAddress, null);
}
} catch (err) {
try {
errorMessage = JSON.parse(err.message).message;
} catch (_) {
errorMessage = err.message;
}
}
}
}

async function leave_view() {
if ($selectedResource !== undefined) {
const ancestors = await $selectedResource.get_ancestors(null);
for (const ancestor of ancestors) {
resourceNodeDataMap[ancestor.get_id()].collapsed = false;
}
}
modifierView = undefined;
}
</script>

<div class="container">
<div class="inputs">
<p>
Searching for descendants of {searchTarget.get_caption()} ({searchTarget.get_id()})
whose virtual address matches a specific address or lies in a range of
addresses.
</p>
{#if rangeSearch}
<label>
Min virtual address:
<input type="text" bind:value="{searchRangeStartInput}" />
</label>
<label>
Max virtual address:
<input type="text" bind:value="{searchRangeEndInput}" />
</label>
{:else}
<label>
Virtual address:
<input type="text" bind:value="{searchInput}" />
</label>
{/if}
<div class="row">
<label>
Range
<input type="checkbox" bind:checked="{rangeSearch}" />
</label>
</div>
{#if errorMessage}
<p class="error">
Error:
{errorMessage}
</p>
{/if}
</div>
<div class="actions">
<button on:click="{search}">Search</button>
<button on:click="{leave_view}">Cancel</button>
</div>
<div class="results">
<p>Found {results.length} results</p>
</div>
{#each results as matched_resource}
<div class="resultsbox">
<ResourceTreeNode
rootResource="{matched_resource}"
collapsed="false"
bind:resourceNodeDataMap
/>
</div>
{/each}
</div>
16 changes: 16 additions & 0 deletions frontend/src/ofrak/remote_resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,22 @@ export class RemoteResource extends Resource {
});
}

async search_for_vaddr(vaddr_start, vaddr_end) {
const matching_models = await fetch(`${this.uri}/search_for_vaddr`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify([vaddr_start, vaddr_end]),
}).then(async (r) => {
if (!r.ok) {
throw Error(JSON.stringify(await r.json(), undefined, 2));
}
return await r.json();
});
return this._remote_models_to_resources(matching_models);
}

_remote_models_to_resources(remote_models) {
if (remote_models.length === 0) {
return [];
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/ofrak/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ export class Resource {
}
}

async search_for_vaddr(vaddr_start, vaddr_end) {
throw new NotImplementedError("search_for_vaddr");
}

/***
* Return the string representation of a comment, which includes its range as prefix if it
* has one.
Expand Down