GT-3294 Added support for DB FixedField with improved indexing.

This commit is contained in:
ghidra1
2020-02-24 18:02:01 -05:00
parent 14d4c87ef4
commit fcb3151f94
224 changed files with 9574 additions and 7913 deletions
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,12 +14,11 @@
* limitations under the License.
*/
// Performs database consistency check on the current program
import db.DBHandle;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.ProgramManager;
import ghidra.framework.model.DomainFile;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.listing.Program;
import db.DBHandle;
public class ConsistencyCheck extends GhidraScript {
@@ -56,6 +54,8 @@ public class ConsistencyCheck extends GhidraScript {
return;
}
monitor.checkCanceled();
if (!df.canSave() || !currentProgram.hasExclusiveAccess()) {
popup("Program database is NOT consistent!\nRebuild requires exclusive checkout.");
return;
@@ -67,19 +67,22 @@ public class ConsistencyCheck extends GhidraScript {
}
end(false);
ProgramDB program = (ProgramDB) df.getDomainObject(this, false, false, monitor);
programMgr.closeProgram(currentProgram, true);
currentProgram = (Program) df.getDomainObject(this, false, false, monitor);
dbh = ((ProgramDB) currentProgram).getDBHandle();
monitor.clearCanceled(); // compensate for Script Manager cancelling task on program close
dbh = program.getDBHandle();
try {
boolean success = false;
long txId = dbh.startTransaction();
int txId = program.startTransaction("Rebuild DB Indexes");
try {
success = dbh.rebuild(monitor);
}
finally {
dbh.endTransaction(txId, success);
program.endTransaction(txId, success);
}
if (!success) {
@@ -92,11 +95,12 @@ public class ConsistencyCheck extends GhidraScript {
return;
}
currentProgram.save("DB Rebuild", monitor);
program.save("DB Rebuild", monitor);
}
finally {
currentProgram.release(this);
currentProgram = programMgr.openProgram(df);
programMgr.openProgram(program);
program.release(this);
currentProgram = program;
start();
}
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +15,6 @@
*/
package ghidra.app.plugin.debug.dbtable;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
import java.io.IOException;
import java.util.*;
@@ -26,6 +22,8 @@ import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import db.*;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
public class DbLargeTableModel implements TableModel {
private ArrayList<TableModelListener> listeners = new ArrayList<TableModelListener>();
@@ -43,7 +41,7 @@ public class DbLargeTableModel implements TableModel {
this.table = table;
schema = table.getSchema();
try {
keyType = schema.getKeyFieldClass().newInstance();
keyType = schema.getKeyFieldType();
}
catch (Exception e) {
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
@@ -59,39 +57,39 @@ public class DbLargeTableModel implements TableModel {
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
}
columns.add(getColumn(schema.getKeyFieldClass()));
columns.add(getColumn(schema.getKeyFieldType()));
Class<?>[] classes = schema.getFieldClasses();
int fieldCount = schema.getFieldCount();
for (int i = 0; i < fieldCount; i++) {
columns.add(getColumn(classes[i]));
Field[] fields = schema.getFields();
for (Field field : fields) {
columns.add(getColumn(field));
}
}
private AbstractColumnAdapter getColumn(Class<?> c) {
if (c == ByteField.class) {
private AbstractColumnAdapter getColumn(Field field) {
if (field instanceof ByteField) {
return new ByteColumnAdapter();
}
else if (c == BooleanField.class) {
else if (field instanceof BooleanField) {
return new BooleanColumnAdapter();
}
else if (c == ShortField.class) {
else if (field instanceof ShortField) {
return new ShortColumnAdapter();
}
else if (c == IntField.class) {
else if (field instanceof IntField) {
return new IntegerColumnAdapter();
}
else if (c == LongField.class) {
else if (field instanceof LongField) {
return new LongColumnAdapter();
}
else if (c == StringField.class) {
else if (field instanceof StringField) {
return new StringColumnAdapter();
}
else if (c == BinaryField.class) {
else if (field instanceof BinaryField) {
return new BinaryColumnAdapter();
}
throw new AssertException("New, unexpected DB column class type: " + c);
throw new AssertException(
"New, unexpected DB column type: " + field.getClass().getSimpleName());
}
private void findMinKey() throws IOException {
@@ -36,12 +36,11 @@ public class DbSmallTableModel extends AbstractSortedTableModel<Record> {
records = new ArrayList<>(table.getRecordCount());
columns.add(getColumn(schema.getKeyFieldClass()));
columns.add(getColumn(schema.getKeyFieldType()));
Class<?>[] classes = schema.getFieldClasses();
int fieldCount = schema.getFieldCount();
for (int i = 0; i < fieldCount; i++) {
columns.add(getColumn(classes[i]));
Field[] fields = schema.getFields();
for (Field field : fields) {
columns.add(getColumn(field));
}
try {
@@ -55,29 +54,30 @@ public class DbSmallTableModel extends AbstractSortedTableModel<Record> {
}
}
private AbstractColumnAdapter getColumn(Class<?> c) {
if (c == ByteField.class) {
private AbstractColumnAdapter getColumn(Field field) {
if (field instanceof ByteField) {
return new ByteColumnAdapter();
}
else if (c == BooleanField.class) {
else if (field instanceof BooleanField) {
return new BooleanColumnAdapter();
}
else if (c == ShortField.class) {
else if (field instanceof ShortField) {
return new ShortColumnAdapter();
}
else if (c == IntField.class) {
else if (field instanceof IntField) {
return new IntegerColumnAdapter();
}
else if (c == LongField.class) {
else if (field instanceof LongField) {
return new LongColumnAdapter();
}
else if (c == StringField.class) {
else if (field instanceof StringField) {
return new StringColumnAdapter();
}
else if (c == BinaryField.class) {
else if (field instanceof BinaryField) {
return new BinaryColumnAdapter();
}
throw new AssertException("New, unexpected DB column class type: " + c);
throw new AssertException(
"New, unexpected DB column type: " + field.getClass().getSimpleName());
}
@Override
@@ -66,7 +66,7 @@ public class AddressIndexPrimaryKeyIteratorTest extends AbstractGhidraHeadedInte
// Create table with indexed address column
Schema schema =
new Schema(0, "id", new Class[] { LongField.class }, new String[] { "addr" });
new Schema(0, "id", new Field[] { LongField.INSTANCE }, new String[] { "addr" });
DBHandle handle = program.getDBHandle();
myTable = handle.createTable("MyTable", schema, new int[] { 0 });
@@ -32,7 +32,7 @@ import ghidra.util.datastruct.LongArray;
public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest {
private static Schema SCHEMA =
new Schema(0, "addr", new Class[] { StringField.class }, new String[] { "str" });
new Schema(0, "addr", new Field[] { StringField.INSTANCE }, new String[] { "str" });
private ProgramDB program;
private AddressSpace space;
@@ -114,7 +114,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator0() throws Exception {
public void testIterator0() throws Exception {
AddressKeyIterator it = new AddressKeyIterator();
assertTrue(!it.hasNext());
assertTrue(!it.hasPrevious());
@@ -133,7 +133,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator1() throws Exception {
public void testIterator1() throws Exception {
int index = 0;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, true);
while (it.hasNext()) {
@@ -144,7 +144,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator2() throws Exception {
public void testIterator2() throws Exception {
int index = 0x10;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x4000), true);
while (it.hasNext()) {
@@ -155,7 +155,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator3() throws Exception {
public void testIterator3() throws Exception {
int index = 0x11;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), false);
while (it.hasNext()) {
@@ -166,7 +166,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator4() throws Exception {
public void testIterator4() throws Exception {
int index = 0x10;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), true);
while (it.hasNext()) {
@@ -177,7 +177,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator5() throws Exception {
public void testIterator5() throws Exception {
int index = 0x0f;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), true);
while (it.hasPrevious()) {
@@ -188,7 +188,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator6() throws Exception {
public void testIterator6() throws Exception {
int index = 0x10;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), false);
while (it.hasPrevious()) {
@@ -199,7 +199,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator7() throws Exception {
public void testIterator7() throws Exception {
AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000));
@@ -216,7 +216,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator8() throws Exception {
public void testIterator8() throws Exception {
AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000));
@@ -233,7 +233,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator9() throws Exception {
public void testIterator9() throws Exception {
AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000));
@@ -247,7 +247,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator10() throws Exception {
public void testIterator10() throws Exception {
AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000));
@@ -264,7 +264,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator11() throws Exception {
public void testIterator11() throws Exception {
AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000));
@@ -279,7 +279,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator12() throws Exception {
public void testIterator12() throws Exception {
int index = 0x3f;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, null, false);
while (it.hasPrevious()) {
@@ -290,7 +290,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIterator13() throws Exception {
public void testIterator13() throws Exception {
AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000));
@@ -304,7 +304,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIteratorCheckWrap1() throws Exception {
public void testIteratorCheckWrap1() throws Exception {
addRecord(addr(0x0));
addRecord(addr(0x0100));
@@ -320,7 +320,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
}
@Test
public void testIteratorCheckWrap2() throws Exception {
public void testIteratorCheckWrap2() throws Exception {
addRecord(addr(0x0));
addRecord(addr(0x0100));
@@ -35,7 +35,8 @@ import ghidra.util.Lock;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitorAdapter;
public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest implements ErrorHandler {
public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest
implements ErrorHandler {
private TestEnv env; // needed to discover languages
private ProgramDB program;
@@ -83,7 +84,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
public void testTransaction() {
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
new Lock("Test"), "TEST", this, LongField.class, true);
new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
try {
map.paintRange(addr(0), addr(0x1000), ONE);
@@ -114,7 +115,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
public void testPaint() {
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
new Lock("Test"), "TEST", this, LongField.class, true);
new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
int id = program.startTransaction("TEST");
try {
@@ -152,7 +153,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
public void testClear() {
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
new Lock("Test"), "TEST", this, LongField.class, true);
new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
int id = program.startTransaction("TEST");
try {
@@ -186,7 +187,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
public void testAddressRangeIterator() {
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
new Lock("Test"), "TEST", this, LongField.class, true);
new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
int id = program.startTransaction("TEST");
try {
@@ -248,7 +249,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
public void testMove() {
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
new Lock("Test"), "TEST", this, LongField.class, true);
new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
int id = program.startTransaction("TEST");
try {
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,57 +20,59 @@ import java.util.Random;
import db.*;
public class DatabaseBenchMarks {
static int BUFFER_SIZE = 16*1024;
static int CACHE_SIZE = 32*1024*1024;
static int BUFFER_SIZE = 16 * 1024;
static int CACHE_SIZE = 32 * 1024 * 1024;
public static void main(String[] args) {
TestTimer timer = new TestTimer();
testOrderedIntInsertions(timer,1000);
testOrderedIntInsertions(timer,10000);
testOrderedIntInsertions(timer,100000);
testOrderedIntInsertions(timer,1000000);
testOrderedIntInsertions(timer, 1000);
testOrderedIntInsertions(timer, 10000);
testOrderedIntInsertions(timer, 100000);
testOrderedIntInsertions(timer, 1000000);
System.out.println("");
testOrderedStringInsertions(timer,1000);
testOrderedStringInsertions(timer,10000);
testOrderedStringInsertions(timer,100000);
testOrderedStringInsertions(timer,1000000);
testOrderedStringInsertions(timer, 1000);
testOrderedStringInsertions(timer, 10000);
testOrderedStringInsertions(timer, 100000);
testOrderedStringInsertions(timer, 1000000);
System.out.println("");
testRandomIntInsertions(timer,1000);
testRandomIntInsertions(timer,10000);
testRandomIntInsertions(timer,100000);
testRandomIntInsertions(timer,1000000);
testRandomIntInsertions(timer, 1000);
testRandomIntInsertions(timer, 10000);
testRandomIntInsertions(timer, 100000);
testRandomIntInsertions(timer, 1000000);
System.out.println("");
testIteration(timer);
System.out.println("");
testRandomAccess(timer);
}
private static void testOrderedIntInsertions(TestTimer timer, int numInsertions) {
try {
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
long transactionID = dbh.startTransaction();
Schema schema = new Schema(1, "Key", new Class[]{IntField.class}, new String[]{"Value"});
Schema schema =
new Schema(1, "Key", new Field[] { IntField.INSTANCE }, new String[] { "Value" });
Table table = dbh.createTable("Test", schema);
Record record = schema.createRecord(0);
timer.start("Inserting "+numInsertions+" sorted records with long keys and integer values");
for(int i=0;i<numInsertions;i++) {
timer.start(
"Inserting " + numInsertions + " sorted records with long keys and integer values");
for (int i = 0; i < numInsertions; i++) {
record.setKey(i);
record.setIntValue(0,i);
record.setIntValue(0, i);
table.putRecord(record);
}
timer.end();
dbh.endTransaction(transactionID, true);
dbh.close();
}catch(IOException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
@@ -79,69 +80,77 @@ public class DatabaseBenchMarks {
try {
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
long transactionID = dbh.startTransaction();
Schema schema = new Schema(1, "Key", new Class[]{StringField.class}, new String[]{"Value"});
Schema schema = new Schema(1, "Key", new Field[] { StringField.INSTANCE },
new String[] { "Value" });
Table table = dbh.createTable("Test", schema);
Record record = schema.createRecord(0);
timer.start("Inserting "+numInsertions+" sorted records with long keys and String (length = 8) values");
for(int i=0;i<numInsertions;i++) {
timer.start("Inserting " + numInsertions +
" sorted records with long keys and String (length = 8) values");
for (int i = 0; i < numInsertions; i++) {
record.setKey(i);
record.setString(0,"abcdefgh");
record.setString(0, "abcdefgh");
table.putRecord(record);
}
timer.end();
dbh.endTransaction(transactionID, true);
dbh.close();
}catch(IOException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
private static void testRandomIntInsertions(TestTimer timer, int numInsertions) {
try {
Random random = new Random();
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
long transactionID = dbh.startTransaction();
Schema schema = new Schema(1, "Key", new Class[]{IntField.class}, new String[]{"Value"});
Schema schema =
new Schema(1, "Key", new Field[] { IntField.INSTANCE }, new String[] { "Value" });
Table table = dbh.createTable("Test", schema);
Record record = schema.createRecord(0);
timer.start("Inserting "+numInsertions+" random records with long keys and integer values");
for(int i=0;i<numInsertions;i++) {
timer.start(
"Inserting " + numInsertions + " random records with long keys and integer values");
for (int i = 0; i < numInsertions; i++) {
record.setKey(random.nextLong());
record.setIntValue(0,i);
record.setIntValue(0, i);
table.putRecord(record);
}
timer.end();
dbh.endTransaction(transactionID, true);
dbh.close();
}catch(IOException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
private static void testIteration(TestTimer timer) {
try {
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
long transactionID = dbh.startTransaction();
Schema schema = new Schema(1, "Key", new Class[]{IntField.class}, new String[]{"Value"});
Schema schema =
new Schema(1, "Key", new Field[] { IntField.INSTANCE }, new String[] { "Value" });
Table table = dbh.createTable("Test", schema);
Record record = schema.createRecord(0);
System.out.print("building database...");
for(int i=0;i<1000000;i++) {
for (int i = 0; i < 1000000; i++) {
record.setKey(i);
record.setIntValue(0,i);
record.setIntValue(0, i);
table.putRecord(record);
}
timer.start("Iterating over 1000000 int records");
RecordIterator it = table.iterator();
while(it.hasNext()) {
while (it.hasNext()) {
it.next();
}
timer.end();
timer.end();
dbh.endTransaction(transactionID, true);
dbh.close();
}catch(IOException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
@@ -149,42 +158,43 @@ public class DatabaseBenchMarks {
try {
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
long transactionID = dbh.startTransaction();
Schema schema = new Schema(1, "Key", new Class[]{IntField.class}, new String[]{"Value"});
Schema schema =
new Schema(1, "Key", new Field[] { IntField.INSTANCE }, new String[] { "Value" });
Table table = dbh.createTable("Test", schema);
Record record = schema.createRecord(0);
System.out.print("building database...");
for(int i=0;i<1000000;i++) {
for (int i = 0; i < 1000000; i++) {
record.setKey(i);
record.setIntValue(0,i);
record.setIntValue(0, i);
table.putRecord(record);
}
Random random = new Random();
timer.start("Randomly accessing 1000000 int records");
for(int i=0;i<1000000;i++) {
for (int i = 0; i < 1000000; i++) {
table.getRecord(random.nextInt(1000000));
}
timer.end();
timer.end();
dbh.endTransaction(transactionID, true);
dbh.close();
}catch(IOException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
class TestTimer {
long start;
void start(String testMsg) {
System.out.print(testMsg+"... ");
System.out.print(testMsg + "... ");
start = System.currentTimeMillis();
}
void end() {
long end = System.currentTimeMillis();
System.out.println(""+(end-start)/1000.0+" seconds");
System.out.println("" + (end - start) / 1000.0 + " seconds");
}
}
File diff suppressed because it is too large Load Diff
@@ -44,11 +44,11 @@ public class FunctionsTable {
static final int CACHE_SIZE = 10000;
// @formatter:off
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Function ID", new Class[] {
ShortField.class, LongField.class,
ByteField.class, LongField.class, LongField.class,
LongField.class, LongField.class, LongField.class,
ByteField.class
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Function ID", new Field[] {
ShortField.INSTANCE, LongField.INSTANCE,
ByteField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE,
LongField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE,
ByteField.INSTANCE
}, new String[] {
"Code Unit Size", "Full Hash",
"Specific Hash Additional Size", "Specific Hash", "Library ID",
@@ -133,14 +133,15 @@ public class FunctionsTable {
*/
public List<FunctionRecord> getFunctionRecordsByFullHash(long hash) throws IOException {
LongField hashField = new LongField(hash);
DBLongIterator iterator = table.indexKeyIterator(FULL_HASH_COL, hashField, hashField, true);
DBFieldIterator iterator =
table.indexKeyIterator(FULL_HASH_COL, hashField, hashField, true);
if (!iterator.hasNext()) {
return Collections.emptyList();
}
List<FunctionRecord> list = new ArrayList<>();
while (iterator.hasNext()) {
long key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key);
Field key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key.getLongValue());
if (functionRecord == null) {
Record record = table.getRecord(key);
functionRecord = new FunctionRecord(fidDb, functionCache, record);
@@ -216,15 +217,15 @@ public class FunctionsTable {
*/
public List<FunctionRecord> getFunctionRecordsByNameSubstring(String nameSearch)
throws IOException {
DBLongIterator iterator = table.indexKeyIterator(NAME_ID_COL);
DBFieldIterator iterator = table.indexKeyIterator(NAME_ID_COL);
if (!iterator.hasNext()) {
return Collections.emptyList();
}
List<FunctionRecord> list = new ArrayList<>();
while (iterator.hasNext()) {
long key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key);
Field key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key.getLongValue());
if (functionRecord == null) {
Record record = table.getRecord(key);
long nameID = record.getLongValue(NAME_ID_COL);
@@ -255,15 +256,15 @@ public class FunctionsTable {
*/
public List<FunctionRecord> getFunctionRecordsByNameRegex(String regex) throws IOException {
Matcher matcher = Pattern.compile(regex).matcher("");
DBLongIterator iterator = table.indexKeyIterator(NAME_ID_COL);
DBFieldIterator iterator = table.indexKeyIterator(NAME_ID_COL);
if (!iterator.hasNext()) {
return Collections.emptyList();
}
List<FunctionRecord> list = new ArrayList<>();
while (iterator.hasNext()) {
long key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key);
Field key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key.getLongValue());
if (functionRecord == null) {
Record record = table.getRecord(key);
long nameID = record.getLongValue(NAME_ID_COL);
@@ -347,15 +348,15 @@ public class FunctionsTable {
return Collections.emptyList();
}
LongField field = new LongField(stringID);
DBLongIterator iterator = table.indexKeyIterator(NAME_ID_COL, field, field, true);
DBFieldIterator iterator = table.indexKeyIterator(NAME_ID_COL, field, field, true);
if (!iterator.hasNext()) {
return Collections.emptyList();
}
final long libraryKey = library.getLibraryID();
List<FunctionRecord> list = new ArrayList<>();
while (iterator.hasNext()) {
long key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key);
Field key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key.getLongValue());
if (functionRecord == null) {
Record record = table.getRecord(key);
if (record.getLongValue(LIBRARY_ID_COL) == libraryKey) {
@@ -49,10 +49,10 @@ public class LibrariesTable {
static final int GHIDRA_COMPILER_SPEC_ID_COL = 7;
// @formatter:off
static final Schema SCHEMA = new Schema(VERSION, "Library ID", new Class[] {
StringField.class, StringField.class, StringField.class,
StringField.class, StringField.class, IntField.class, IntField.class,
StringField.class
static final Schema SCHEMA = new Schema(VERSION, "Library ID", new Field[] {
StringField.INSTANCE, StringField.INSTANCE, StringField.INSTANCE,
StringField.INSTANCE, StringField.INSTANCE, IntField.INSTANCE, IntField.INSTANCE,
StringField.INSTANCE
}, new String[] {
"Library Family Name", "Library Version", "Library Variant",
"Ghidra Version", "Ghidra Language ID", "Ghidra Language Version", "Ghidra Language Minor Version",
@@ -90,8 +90,9 @@ public class LibrariesTable {
if (libraryVersion != VERSION) {
String msg = "Expected version " + VERSION + " for table " + LIBRARIES_TABLE +
" but got " + table.getSchema().getVersion();
throw new VersionException(msg, libraryVersion < VERSION
? VersionException.OLDER_VERSION : VersionException.NEWER_VERSION,
throw new VersionException(msg,
libraryVersion < VERSION ? VersionException.OLDER_VERSION
: VersionException.NEWER_VERSION,
false);
}
}
@@ -155,14 +156,14 @@ public class LibrariesTable {
public List<LibraryRecord> getLibrariesByName(String name, String version, String variant)
throws IOException {
StringField hashField = new StringField(name);
DBLongIterator iterator =
DBFieldIterator iterator =
table.indexKeyIterator(LIBRARY_FAMILY_NAME_COL, hashField, hashField, true);
if (!iterator.hasNext()) {
return Collections.emptyList();
}
List<LibraryRecord> list = new ArrayList<LibraryRecord>();
while (iterator.hasNext()) {
long key = iterator.next();
Field key = iterator.next();
Record record = table.getRecord(key);
LibraryRecord libraryRecord = new LibraryRecord(record);
if (version != null) {
@@ -27,8 +27,8 @@ public class RelationsTable {
// static final int CACHE_SIZE = 10000;
// @formatter:off
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Relation Smash", new Class[] {
}, new String[] {
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Relation Smash",
new Field[] { }, new String[] {
});
// @formatter:on
@@ -35,11 +35,9 @@ public class StringsTable {
static final int CACHE_SIZE = 10000;
// @formatter:off
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "String ID", new Class[] {
StringField.class
}, new String[] {
"String Value"
});
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "String ID",
new Field[] { StringField.INSTANCE },
new String[] { "String Value" });
// @formatter:on
static int[] INDEXED_COLUMNS = new int[] { STRING_VALUE_COL };
@@ -69,29 +67,30 @@ public class StringsTable {
* @throws IOException if the database has a problem
*/
long obtainStringID(String value) throws IOException {
long[] records = table.findRecords(new StringField(value), STRING_VALUE_COL);
Field[] records = table.findRecords(new StringField(value), STRING_VALUE_COL);
if (records == null || records.length == 0) {
// create
Record record = SCHEMA.createRecord(UniversalIdGenerator.nextID().getValue());
long key = UniversalIdGenerator.nextID().getValue();
Record record = SCHEMA.createRecord(key);
record.setString(STRING_VALUE_COL, value);
table.putRecord(record);
return record.getKey();
return key;
}
return records[0];
return records[0].getLongValue();
}
/**
* Lookup existing ID or return null for String value.
* @param value the string value
* @return the existing interned string primary key, or null if nonexistent
* @return the existing interned string primary key as LongField, or null if nonexistent
* @throws IOException if the database has a problem
*/
Long lookupStringID(String value) throws IOException {
long[] records = table.findRecords(new StringField(value), STRING_VALUE_COL);
Field[] records = table.findRecords(new StringField(value), STRING_VALUE_COL);
if (records == null || records.length == 0) {
return null;
}
return records[0];
return records[0].getLongValue();
}
/**
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,41 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db.util;
package ghidra.feature.vt.api.db;
import db.Field;
public class TableColumn {
private final Class<? extends Field> columnClass;
private final Field columnField;
private boolean indexed;
private int ordinal;
private String name;
public TableColumn( Class<? extends Field> columnClass ) {
this( columnClass, false );
public TableColumn(Field columnField) {
this(columnField, false);
}
public TableColumn( Class<? extends Field> columnClass, boolean isIndexed ) {
this.columnClass = columnClass;
public TableColumn(Field columnField, boolean isIndexed) {
this.columnField = columnField;
indexed = isIndexed;
}
void setName( String name ) {
this.name = name;
void setName(String name) {
this.name = name;
}
void setOrdinal( int ordinal ) {
void setOrdinal(int ordinal) {
this.ordinal = ordinal;
}
public boolean isIndexed() {
return indexed;
}
public Class<? extends Field> getColumnClass() {
return columnClass;
public Field getColumnField() {
return columnField;
}
public String name() {
@@ -61,6 +60,6 @@ public class TableColumn {
@Override
public String toString() {
return name() + "("+ ordinal +")";
return name() + "(" + ordinal + ")";
}
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,63 +13,61 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db.util;
import ghidra.util.Msg;
package ghidra.feature.vt.api.db;
import java.util.*;
import db.Field;
import ghidra.util.Msg;
public class TableDescriptor {
private TableColumn[] columns;
protected TableDescriptor() {
this.columns = discoverTableColumns();
}
private TableColumn[] discoverTableColumns() {
Class<? extends TableDescriptor> clazz = getClass();
java.lang.reflect.Field[] fields = clazz.getFields();
List<TableColumn> list = new ArrayList<TableColumn>(fields.length);
for ( java.lang.reflect.Field field : fields ) {
for (java.lang.reflect.Field field : fields) {
Class<?> type = field.getType();
if ( !TableColumn.class.isAssignableFrom( type ) ) {
if (!TableColumn.class.isAssignableFrom(type)) {
continue;
}
try {
TableColumn column = (TableColumn) field.get( null );
column.setName( field.getName() );
column.setOrdinal( list.size() );
list.add( column );
TableColumn column = (TableColumn) field.get(null);
column.setName(field.getName());
column.setOrdinal(list.size());
list.add(column);
}
catch ( IllegalArgumentException e ) {
catch (IllegalArgumentException e) {
// shouldn't happen
}
catch ( IllegalAccessException e ) {
Msg.showError( this, null, "Class Usage Error", "You must provide public " +
"static members for your TableColumns" );
catch (IllegalAccessException e) {
Msg.showError(this, null, "Class Usage Error",
"You must provide public " + "static members for your TableColumns");
}
}
return list.toArray( new TableColumn[list.size()] );
return list.toArray(new TableColumn[list.size()]);
}
public int[] getIndexedColumns() {
int count = 0;
for ( TableColumn column : columns ) {
for (TableColumn column : columns) {
if (column.isIndexed()) {
count++;
}
}
int[] indexedColumns = new int[count];
count = 0;
for ( TableColumn column : columns ) {
for (TableColumn column : columns) {
if (column.isIndexed()) {
indexedColumns[count++] = column.column();
}
@@ -80,19 +77,18 @@ public class TableDescriptor {
public String[] getColumnNames() {
List<String> list = new LinkedList<String>();
for ( TableColumn column : columns ) {
list.add( column.name() );
for (TableColumn column : columns) {
list.add(column.name());
}
return list.toArray( new String[ columns.length ] );
return list.toArray(new String[columns.length]);
}
@SuppressWarnings("unchecked") // we know our class types are safe
public Class<? extends Field>[] getColumnClasses() {
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>();
for ( TableColumn column : columns ) {
list.add( column.getColumnClass() );
}
return list.toArray( new Class[ columns.length ] );
public Field[] getColumnFields() {
Field[] fields = new Field[columns.length];
for (int i = 0; i < fields.length; i++) {
fields[i] = columns[i].getColumnField().newField();
}
return fields;
}
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,48 +16,50 @@
package ghidra.feature.vt.api.db;
import static ghidra.feature.vt.api.db.VTAddressCorrelatorAdapter.AddressCorrelationTableDescriptor.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import java.io.File;
import java.io.IOException;
import java.util.List;
import db.*;
import db.util.TableColumn;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public abstract class VTAddressCorrelatorAdapter {
public static class AddressCorrelationTableDescriptor extends db.util.TableDescriptor {
public static class AddressCorrelationTableDescriptor
extends ghidra.feature.vt.api.db.TableDescriptor {
public static TableColumn SOURCE_ENTRY_COL = new TableColumn(LongField.class, true);
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.class);
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.class);
public static AddressCorrelationTableDescriptor INSTANCE = new AddressCorrelationTableDescriptor();
public static TableColumn SOURCE_ENTRY_COL = new TableColumn(LongField.INSTANCE, true);
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
public static AddressCorrelationTableDescriptor INSTANCE =
new AddressCorrelationTableDescriptor();
}
static String TABLE_NAME = "AddressCorrelationTable";
static Schema TABLE_SCHEMA = new Schema(0, "Key",
INSTANCE.getColumnClasses(), INSTANCE.getColumnNames());
static Schema TABLE_SCHEMA =
new Schema(0, "Key", INSTANCE.getColumnFields(), INSTANCE.getColumnNames());
static int[] TABLE_INDEXES = INSTANCE.getIndexedColumns();
private DBHandle dbHandle;
protected VTAddressCorrelatorAdapter(DBHandle dbHandle) {
this.dbHandle = dbHandle;
}
public static VTAddressCorrelatorAdapter createAdapter(DBHandle dbHandle) throws IOException {
return new VTAddressCorrelationAdapterV0(dbHandle);
}
public static VTAddressCorrelatorAdapter getAdapter(DBHandle dbHandle, TaskMonitor monitor)
public static VTAddressCorrelatorAdapter getAdapter(DBHandle dbHandle, TaskMonitor monitor)
throws VersionException {
return new VTAddressCorrelationAdapterV0(dbHandle, monitor);
}
abstract void createAddressRecord(long sourceEntryLong, long sourceLong, long destinationLong) throws IOException;
abstract void createAddressRecord(long sourceEntryLong, long sourceLong, long destinationLong)
throws IOException;
abstract List<Record> getAddressRecords(long sourceEntryLong) throws IOException;
@@ -69,8 +70,9 @@ public abstract class VTAddressCorrelatorAdapter {
void save(TaskMonitor monitor) throws CancelledException, IOException {
dbHandle.save("", null, monitor);
}
void saveAs(File file, TaskMonitor monitor) throws CancelledException, IOException {
dbHandle.saveAs(file, true, monitor);
}
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,35 +15,36 @@
*/
package ghidra.feature.vt.api.db;
import static ghidra.feature.vt.api.db.VTAssociationTableDBAdapter.AssociationTableDescriptor.INSTANCE;
import ghidra.feature.vt.api.main.VTAssociationStatus;
import ghidra.feature.vt.api.main.VTAssociationType;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import static ghidra.feature.vt.api.db.VTAssociationTableDBAdapter.AssociationTableDescriptor.*;
import java.io.IOException;
import java.util.Set;
import db.*;
import db.util.TableColumn;
import ghidra.feature.vt.api.main.VTAssociationStatus;
import ghidra.feature.vt.api.main.VTAssociationType;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public abstract class VTAssociationTableDBAdapter {
public static class AssociationTableDescriptor extends db.util.TableDescriptor {
public static class AssociationTableDescriptor
extends ghidra.feature.vt.api.db.TableDescriptor {
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.class, true);
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.class, true);
public static TableColumn TYPE_COL = new TableColumn(ByteField.class);
public static TableColumn STATUS_COL = new TableColumn(ByteField.class);
public static TableColumn APPLIED_STATUS_COL = new TableColumn(ByteField.class);
public static TableColumn VOTE_COUNT_COL = new TableColumn(IntField.class);
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.INSTANCE, true);
public static TableColumn DESTINATION_ADDRESS_COL =
new TableColumn(LongField.INSTANCE, true);
public static TableColumn TYPE_COL = new TableColumn(ByteField.INSTANCE);
public static TableColumn STATUS_COL = new TableColumn(ByteField.INSTANCE);
public static TableColumn APPLIED_STATUS_COL = new TableColumn(ByteField.INSTANCE);
public static TableColumn VOTE_COUNT_COL = new TableColumn(IntField.INSTANCE);
public static AssociationTableDescriptor INSTANCE = new AssociationTableDescriptor();
}
static String TABLE_NAME = "AssociationTable";
static Schema TABLE_SCHEMA =
new Schema(0, "Key", INSTANCE.getColumnClasses(), INSTANCE.getColumnNames());
new Schema(0, "Key", INSTANCE.getColumnFields(), INSTANCE.getColumnNames());
static int[] TABLE_INDEXES = INSTANCE.getIndexedColumns();
public static VTAssociationTableDBAdapter createAdapter(DBHandle dbHandle) throws IOException {
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,36 +15,35 @@
*/
package ghidra.feature.vt.api.db;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.INSTANCE;
import ghidra.feature.vt.api.impl.MarkupItemStorage;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.*;
import java.io.IOException;
import db.*;
import db.util.TableColumn;
import ghidra.feature.vt.api.impl.MarkupItemStorage;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public abstract class VTMatchMarkupItemTableDBAdapter {
public static class MarkupTableDescriptor extends db.util.TableDescriptor {
public static TableColumn ASSOCIATION_KEY_COL = new TableColumn(LongField.class, true);
public static TableColumn ADDRESS_SOURCE_COL = new TableColumn(StringField.class);
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.class);
public static TableColumn MARKUP_TYPE_COL = new TableColumn(ShortField.class);
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.class);
public static TableColumn SOURCE_VALUE_COL = new TableColumn(StringField.class);
public static class MarkupTableDescriptor extends ghidra.feature.vt.api.db.TableDescriptor {
public static TableColumn ASSOCIATION_KEY_COL = new TableColumn(LongField.INSTANCE, true);
public static TableColumn ADDRESS_SOURCE_COL = new TableColumn(StringField.INSTANCE);
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
public static TableColumn MARKUP_TYPE_COL = new TableColumn(ShortField.INSTANCE);
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
public static TableColumn SOURCE_VALUE_COL = new TableColumn(StringField.INSTANCE);
public static TableColumn ORIGINAL_DESTINATION_VALUE_COL =
new TableColumn(StringField.class);
public static TableColumn STATUS_COL = new TableColumn(ByteField.class);
public static TableColumn STATUS_DESCRIPTION_COL = new TableColumn(StringField.class);
new TableColumn(StringField.INSTANCE);
public static TableColumn STATUS_COL = new TableColumn(ByteField.INSTANCE);
public static TableColumn STATUS_DESCRIPTION_COL = new TableColumn(StringField.INSTANCE);
public static MarkupTableDescriptor INSTANCE = new MarkupTableDescriptor();
}
protected static String TABLE_NAME = "MatchMarkupItemTable";
static Schema TABLE_SCHEMA =
new Schema(0, "Key", INSTANCE.getColumnClasses(), INSTANCE.getColumnNames());
new Schema(0, "Key", INSTANCE.getColumnFields(), INSTANCE.getColumnNames());
protected static int[] INDEXED_COLUMNS = INSTANCE.getIndexedColumns();
@@ -71,6 +69,5 @@ public abstract class VTMatchMarkupItemTableDBAdapter {
public abstract int getRecordCount();
public abstract Record createMarkupItemRecord(MarkupItemStorage markupItem)
throws IOException;
public abstract Record createMarkupItemRecord(MarkupItemStorage markupItem) throws IOException;
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,34 +15,33 @@
*/
package ghidra.feature.vt.api.db;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import db.*;
import ghidra.feature.vt.api.main.VTProgramCorrelator;
import ghidra.program.database.map.AddressMap;
import ghidra.program.model.address.AddressSet;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import db.*;
public abstract class VTMatchSetTableDBAdapter {
public enum ColumnDescription {
CORRELATOR_CLASS_COL(StringField.class),
CORRELATOR_NAME_COL(StringField.class),
OPTIONS_COL(StringField.class);
CORRELATOR_CLASS_COL(StringField.INSTANCE),
CORRELATOR_NAME_COL(StringField.INSTANCE),
OPTIONS_COL(StringField.INSTANCE);
private final Class<? extends Field> columnClass;
private final Field columnField;
private ColumnDescription(Class<? extends Field> columnClass) {
this.columnClass = columnClass;
private ColumnDescription(Field columnField) {
this.columnField = columnField;
}
public Class<? extends Field> getColumnClass() {
return columnClass;
public Field getColumnField() {
return columnField;
}
public int column() {
@@ -59,20 +57,18 @@ public abstract class VTMatchSetTableDBAdapter {
return list.toArray(new String[columns.length]);
}
@SuppressWarnings("unchecked")
// we know our class types are safe
private static Class<? extends Field>[] getColumnClasses() {
private static Field[] getColumnFields() {
ColumnDescription[] columns = ColumnDescription.values();
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>();
for (ColumnDescription column : columns) {
list.add(column.getColumnClass());
Field[] fields = new Field[columns.length];
for (int i = 0; i < fields.length; i++) {
fields[i] = columns[i].getColumnField();
}
return list.toArray(new Class[columns.length]);
return fields;
}
}
static String TABLE_NAME = "MatchSetTable";
static Schema TABLE_SCHEMA = new Schema(0, "Key", ColumnDescription.getColumnClasses(),
static Schema TABLE_SCHEMA = new Schema(0, "Key", ColumnDescription.getColumnFields(),
ColumnDescription.getColumnNames());
static VTMatchSetTableDBAdapter createAdapter(DBHandle dbHandle) throws IOException {
@@ -16,13 +16,6 @@
package ghidra.feature.vt.api.db;
import static ghidra.feature.vt.api.db.VTMatchSetTableDBAdapter.ColumnDescription.*;
import ghidra.feature.vt.api.main.VTProgramCorrelator;
import ghidra.framework.options.ToolOptions;
import ghidra.program.database.map.AddressMap;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.VersionException;
import ghidra.util.xml.GenericXMLOutputter;
import java.io.IOException;
import java.io.StringWriter;
@@ -31,13 +24,20 @@ import org.jdom.Element;
import org.jdom.output.XMLOutputter;
import db.*;
import ghidra.feature.vt.api.main.VTProgramCorrelator;
import ghidra.framework.options.ToolOptions;
import ghidra.program.database.map.AddressMap;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.VersionException;
import ghidra.util.xml.GenericXMLOutputter;
public class VTMatchSetTableDBAdapterV0 extends VTMatchSetTableDBAdapter {
private Table table;
private static final Schema STORED_ADDRESS_RANGE_SCHEMA = new Schema(0, "Key", new Class[] {
LongField.class, LongField.class }, new String[] { "addr1", "addr2" });
private static final Schema STORED_ADDRESS_RANGE_SCHEMA = new Schema(0, "Key",
new Field[] { LongField.INSTANCE, LongField.INSTANCE }, new String[] { "addr1", "addr2" });
private final DBHandle dbHandle;
@@ -46,7 +46,8 @@ public class VTMatchSetTableDBAdapterV0 extends VTMatchSetTableDBAdapter {
table = dbHandle.createTable(TABLE_NAME, TABLE_SCHEMA);
}
public VTMatchSetTableDBAdapterV0(DBHandle dbHandle, OpenMode openMode) throws VersionException {
public VTMatchSetTableDBAdapterV0(DBHandle dbHandle, OpenMode openMode)
throws VersionException {
this.dbHandle = dbHandle;
table = dbHandle.getTable(TABLE_NAME);
if (table == null) {
@@ -59,7 +60,8 @@ public class VTMatchSetTableDBAdapterV0 extends VTMatchSetTableDBAdapter {
}
@Override
public Record createMatchSetRecord(long key, VTProgramCorrelator correlator) throws IOException {
public Record createMatchSetRecord(long key, VTProgramCorrelator correlator)
throws IOException {
Record record = TABLE_SCHEMA.createRecord(key);
record.setString(CORRELATOR_CLASS_COL.column(), correlator.getClass().getName());
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,36 +15,35 @@
*/
package ghidra.feature.vt.api.db;
import ghidra.feature.vt.api.main.VTMatchInfo;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import db.*;
import ghidra.feature.vt.api.main.VTMatchInfo;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public abstract class VTMatchTableDBAdapter {
public enum ColumnDescription {
TAG_KEY_COL(LongField.class),
MATCH_SET_COL(LongField.class),
SIMILARITY_SCORE_COL(StringField.class),
CONFIDENCE_SCORE_COL(StringField.class),
LENGTH_TYPE(StringField.class),
SOURCE_LENGTH_COL(IntField.class),
DESTINATION_LENGTH_COL(IntField.class),
ASSOCIATION_COL(LongField.class);
TAG_KEY_COL(LongField.INSTANCE),
MATCH_SET_COL(LongField.INSTANCE),
SIMILARITY_SCORE_COL(StringField.INSTANCE),
CONFIDENCE_SCORE_COL(StringField.INSTANCE),
LENGTH_TYPE(StringField.INSTANCE),
SOURCE_LENGTH_COL(IntField.INSTANCE),
DESTINATION_LENGTH_COL(IntField.INSTANCE),
ASSOCIATION_COL(LongField.INSTANCE);
private final Class<? extends Field> columnClass;
private final Field columnField;
private ColumnDescription(Class<? extends Field> columnClass) {
this.columnClass = columnClass;
private ColumnDescription(Field columnField) {
this.columnField = columnField;
}
public Class<? extends Field> getColumnClass() {
return columnClass;
public Field getColumnField() {
return columnField;
}
public int column() {
@@ -61,22 +59,19 @@ public abstract class VTMatchTableDBAdapter {
return list.toArray(new String[columns.length]);
}
@SuppressWarnings("unchecked")
// we know our class types are safe
private static Class<? extends Field>[] getColumnClasses() {
private static Field[] getColumnFields() {
ColumnDescription[] columns = ColumnDescription.values();
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>();
for (ColumnDescription column : columns) {
list.add(column.getColumnClass());
Field[] fields = new Field[columns.length];
for (int i = 0; i < fields.length; i++) {
fields[i] = columns[i].getColumnField();
}
return list.toArray(new Class[columns.length]);
return fields;
}
}
static String TABLE_NAME = "MatchTable";
static Schema TABLE_SCHEMA =
new Schema(0, "Key", ColumnDescription.getColumnClasses(),
ColumnDescription.getColumnNames());
static Schema TABLE_SCHEMA = new Schema(0, "Key", ColumnDescription.getColumnFields(),
ColumnDescription.getColumnNames());
static VTMatchTableDBAdapter createAdapter(DBHandle dbHandle, long tableID) throws IOException {
return new VTMatchTableDBAdapterV0(dbHandle, tableID);
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +15,13 @@
*/
package ghidra.feature.vt.api.db;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import db.*;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* Abstract adapter for the database table that holds tags for version tracking matches.
@@ -31,16 +29,16 @@ import db.*;
public abstract class VTMatchTagDBAdapter {
public enum ColumnDescription {
TAG_NAME_COL(StringField.class);
TAG_NAME_COL(StringField.INSTANCE);
private final Class<? extends Field> columnClass;
private final Field columnField;
private ColumnDescription(Class<? extends Field> columnClass) {
this.columnClass = columnClass;
private ColumnDescription(Field columnField) {
this.columnField = columnField;
}
public Class<? extends Field> getColumnClass() {
return columnClass;
public Field getColumnField() {
return columnField;
}
public int column() {
@@ -56,22 +54,19 @@ public abstract class VTMatchTagDBAdapter {
return list.toArray(new String[columns.length]);
}
@SuppressWarnings("unchecked")
// we know our class types are safe
private static Class<? extends Field>[] getColumnClasses() {
private static Field[] getColumnFields() {
ColumnDescription[] columns = ColumnDescription.values();
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>();
for (ColumnDescription column : columns) {
list.add(column.getColumnClass());
Field[] fields = new Field[columns.length];
for (int i = 0; i < fields.length; i++) {
fields[i] = columns[i].getColumnField();
}
return list.toArray(new Class[columns.length]);
return fields;
}
}
static String TABLE_NAME = "MatchTagTable";
static Schema TABLE_SCHEMA =
new Schema(0, LongField.class, "Key", ColumnDescription.getColumnClasses(),
ColumnDescription.getColumnNames());
static Schema TABLE_SCHEMA = new Schema(0, "Key", ColumnDescription.getColumnFields(),
ColumnDescription.getColumnNames());
static VTMatchTagDBAdapter createAdapter(DBHandle dbHandle) throws IOException {
return new VTMatchTagDBAdapterV0(dbHandle);
@@ -39,10 +39,10 @@ import ghidra.util.exception.*;
import ghidra.util.task.*;
public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTChangeManager {
private final static Class<?>[] COL_CLASS = new Class[] { StringField.class };
private final static Field[] COL_FIELDS = new Field[] { StringField.INSTANCE };
private final static String[] COL_TYPES = new String[] { "Value" };
private final static Schema SCHEMA =
new Schema(0, StringField.class, "Key", COL_CLASS, COL_TYPES);
new Schema(0, StringField.INSTANCE, "Key", COL_FIELDS, COL_TYPES);
private static final String PROGRAM_ID_PROPERTYLIST_NAME = "ProgramIDs";
private static final String SOURCE_PROGRAM_ID_PROPERTY_KEY = "SourceProgramID";
@@ -55,7 +55,24 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTC
private static final long IMPLIED_MATCH_SET_ID = -1;
private static final String PROPERTY_TABLE_NAME = "PropertyTable";
private static final String DB_VERSION_PROPERTY_NAME = "DB_VERSION";
private static final int DB_VERSION = 1;
/**
* DB_VERSION should be incremented any time a change is made to the overall
* database schema associated with any of the adapters.
* 14-Nov-2019 - version 2 - Corrected fixed length indexing implementation causing
* change in index table low-level storage for newly
* created tables.
*/
private static final int DB_VERSION = 2;
/**
* UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION any time the
* latest version requires a forced upgrade (i.e., Read-only mode not supported
* until upgrade is performed). It is assumed that read-only mode is supported
* if the data's version is >= UPGRADE_REQUIRED_BEFORE_VERSION and <= DB_VERSION.
*/
// NOTE: Schema upgrades are not currently supported
private static final int UPGRADE_REQUIRED_BEFORE_VERSION = 1;
private VTMatchSetTableDBAdapter matchSetTableAdapter;
private AssociationDatabaseManager associationManager;
@@ -78,12 +95,11 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTC
int ID = session.startTransaction("Constructing New Version Tracking Match Set");
try {
session.propertyTable = createPropertyTable(session.getDBHandle());
session.matchSetTableAdapter =
VTMatchSetTableDBAdapter.createAdapter(session.getDBHandle());
session.propertyTable = session.dbh.createTable(PROPERTY_TABLE_NAME, SCHEMA);
session.matchSetTableAdapter = VTMatchSetTableDBAdapter.createAdapter(session.dbh);
session.associationManager =
AssociationDatabaseManager.createAssociationManager(session.getDBHandle(), session);
session.matchTagAdapter = VTMatchTagDBAdapter.createAdapter(session.getDBHandle());
AssociationDatabaseManager.createAssociationManager(session.dbh, session);
session.matchTagAdapter = VTMatchTagDBAdapter.createAdapter(session.dbh);
session.initializePrograms(sourceProgram, destinationProgram);
session.createMatchSet(
new ManualMatchProgramCorrelator(sourceProgram, destinationProgram),
@@ -91,6 +107,7 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTC
session.createMatchSet(
new ImpliedMatchProgramCorrelator(sourceProgram, destinationProgram),
IMPLIED_MATCH_SET_ID);
session.updateVersion();
}
finally {
session.endTransaction(ID, true);
@@ -105,21 +122,29 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTC
return session;
}
private static Table createPropertyTable(DBHandle dbh) throws IOException {
Table table = dbh.createTable(PROPERTY_TABLE_NAME, SCHEMA);
private void updateVersion() throws IOException {
Record record = SCHEMA.createRecord(new StringField(DB_VERSION_PROPERTY_NAME));
record.setString(0, Integer.toString(DB_VERSION));
table.putRecord(record);
return table;
propertyTable.putRecord(record);
}
public static VTSessionDB getVTSession(DBHandle dbHandle, OpenMode openMode, Object consumer,
TaskMonitor monitor) throws VersionException, IOException {
VTSessionDB session = new VTSessionDB(dbHandle, consumer);
if (session.getVersion() < DB_VERSION) {
throw new VersionException("Version Tracking Sessions do not support upgrades.");
int storedVersion = session.getVersion();
if (storedVersion > DB_VERSION) {
throw new VersionException(VersionException.NEWER_VERSION, false);
}
// The following version logic holds true for DB_VERSION=2 which assumes no additional
// DB index tables will be added when open for update/upgrade. This will not hold
// true for future revisions associated with table schema changes in which case the
// UPGRADE_REQUIRED_BEFORE_VERSION value should equal DB_VERSION.
if (storedVersion < UPGRADE_REQUIRED_BEFORE_VERSION) {
throw new VersionException("Version Tracking Sessions do not support schema upgrades.");
}
session.matchSetTableAdapter =
VTMatchSetTableDBAdapter.getAdapter(session.getDBHandle(), openMode, monitor);
session.associationManager =
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,12 +15,11 @@
*/
package db;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException;
import db.buffers.DataBuffer;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* <code>BTreeNode</code> defines a common interface for all types
@@ -30,17 +28,22 @@ import db.buffers.DataBuffer;
interface BTreeNode {
/**
* Return the data buffer ID associated with this node.
* @return the parent node or null if this is the root
*/
public InteriorNode getParent();
/**
* @return the data buffer ID associated with this node.
*/
public int getBufferId();
/**
* Return the data buffer associated with this node.
* @return the data buffer associated with this node.
*/
public DataBuffer getBuffer();
/**
* Return the number of keys contained within this node.
* @return the number of keys contained within this node.
*/
public int getKeyCount();
@@ -50,6 +53,26 @@ interface BTreeNode {
*/
public void setKeyCount(int cnt);
/**
* Get the key value at a specific index.
* @param index key index
* @return key value
* @throws IOException thrown if an IO error occurs
*/
public Field getKeyField(int index) throws IOException;
/**
* Perform a binary search to locate the specified key and derive an index
* into the Buffer ID storage. This method is intended to find the insertion
* index or exact match for a child key. A negative value will be returned
* when an exact match is not found and may be transformed into an
* insertion index (insetIndex = -returnedIndex-1).
* @param key key to search for
* @return int buffer ID index.
* @throws IOException thrown if an IO error occurs
*/
public int getKeyIndex(Field key) throws IOException;
/**
* Delete this node and all child nodes.
* @throws IOException thrown if IO error occurs
@@ -67,11 +90,12 @@ interface BTreeNode {
* Check the consistency of this node and all of its children.
* @return true if consistency check passed, else false
* @param tableName name of table containing this node
* @param monitor
* @throws IOException
* @param monitor task monitor
* @throws IOException if IO error occured
* @throws CancelledException if task cancelled
* @{@link ThrowsTag} CancelledException
*/
public boolean isConsistent(String tableName, TaskMonitor monitor) throws IOException,
CancelledException;
public boolean isConsistent(String tableName, TaskMonitor monitor)
throws IOException, CancelledException;
}
@@ -15,11 +15,11 @@
*/
package db;
import ghidra.util.exception.AssertException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import ghidra.util.exception.AssertException;
/**
* Allows various non-database supported data types to be
* encoded within a BinaryField which may be stored within the
@@ -31,63 +31,63 @@ import java.util.ArrayList;
* support a byte array.
*/
public class BinaryCodedField extends BinaryField {
/**
* byte[] data type
*/
public static final byte BYTE_ARRAY = 0;
/**
* float data type
*/
public static final byte FLOAT = 1;
/**
* double data type
*/
public static final byte DOUBLE = 2;
/**
* short data type
*/
public static final byte SHORT_ARRAY = 3;
/**
* int[] data type
*/
public static final byte INT_ARRAY = 4;
/**
* long[] data type
*/
public static final byte LONG_ARRAY = 5;
/**
* float[] data type
*/
public static final byte FLOAT_ARRAY = 6;
/**
* double[] data type
*/
public static final byte DOUBLE_ARRAY = 7;
/**
* String[] data type
*/
public static final byte STRING_ARRAY = 8;
private static final int DATA_TYPE_OFFSET = 0;
private static final int DATA_OFFSET = 1;
private static final String STRING_ENCODING = "UTF-8";
/**
* Default constructor
*/
BinaryCodedField() {
}
/**
* Construct a coded field from an existing binary field.
* @param binField the binary field
@@ -95,7 +95,7 @@ public class BinaryCodedField extends BinaryField {
public BinaryCodedField(BinaryField binField) {
data = binField.getBinaryData();
}
/**
* Construct a coded field from a double value.
* @param value the double value
@@ -106,7 +106,7 @@ public class BinaryCodedField extends BinaryField {
buffer.putLong(DATA_OFFSET, Double.doubleToLongBits(value));
data = buffer.getData();
}
/**
* Construct a coded field from a float value.
* @param value the float value
@@ -117,7 +117,7 @@ public class BinaryCodedField extends BinaryField {
buffer.putInt(DATA_OFFSET, Float.floatToIntBits(value));
data = buffer.getData();
}
/**
* Construct a coded field from a byte array.
* @param values byte array
@@ -125,121 +125,121 @@ public class BinaryCodedField extends BinaryField {
public BinaryCodedField(byte[] values) {
if (values != null) {
data = new byte[values.length + 2];
data[DATA_OFFSET] = (byte)0;
data[DATA_OFFSET] = (byte) 0;
System.arraycopy(values, 0, data, 2, values.length);
}
else {
data = new byte[2];
data[DATA_OFFSET] = (byte)-1;
data[DATA_OFFSET] = (byte) -1;
}
data[DATA_TYPE_OFFSET] = BYTE_ARRAY;
}
/**
* Construct a coded field from a short array.
* @param values short array
*/
public BinaryCodedField(short[] values) {
int len = (values != null ? (2*values.length) : 0) + 2;
int len = (values != null ? (2 * values.length) : 0) + 2;
BinaryDataBuffer buffer = new BinaryDataBuffer(len);
buffer.putByte(DATA_TYPE_OFFSET, SHORT_ARRAY);
if (values != null) {
int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0);
buffer.putByte(offset++, (byte) 0);
for (int i = 0; i < values.length; i++) {
offset = buffer.putShort(offset, values[i]);
}
}
else {
buffer.putByte(DATA_OFFSET, (byte)-1);
buffer.putByte(DATA_OFFSET, (byte) -1);
}
data = buffer.getData();
}
/**
* Construct a coded field from a int array.
* @param values int array
*/
public BinaryCodedField(int[] values) {
int len = (values != null ? (4*values.length) : 0) + 2;
int len = (values != null ? (4 * values.length) : 0) + 2;
BinaryDataBuffer buffer = new BinaryDataBuffer(len);
buffer.putByte(DATA_TYPE_OFFSET, INT_ARRAY);
if (values != null) {
int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0);
buffer.putByte(offset++, (byte) 0);
for (int i = 0; i < values.length; i++) {
offset = buffer.putInt(offset, values[i]);
}
}
else {
buffer.putByte(DATA_OFFSET, (byte)-1);
buffer.putByte(DATA_OFFSET, (byte) -1);
}
data = buffer.getData();
}
/**
* Construct a coded field from a long array.
* @param values long array
*/
public BinaryCodedField(long[] values) {
int len = (values != null ? (8*values.length) : 0) + 2;
int len = (values != null ? (8 * values.length) : 0) + 2;
BinaryDataBuffer buffer = new BinaryDataBuffer(len);
buffer.putByte(DATA_TYPE_OFFSET, LONG_ARRAY);
if (values != null) {
int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0);
buffer.putByte(offset++, (byte) 0);
for (int i = 0; i < values.length; i++) {
offset = buffer.putLong(offset, values[i]);
}
}
else {
buffer.putByte(DATA_OFFSET, (byte)-1);
buffer.putByte(DATA_OFFSET, (byte) -1);
}
data = buffer.getData();
}
/**
* Construct a coded field from a float array.
* @param values float array
*/
public BinaryCodedField(float[] values) {
int len = (values != null ? (4*values.length) : 0) + 2;
int len = (values != null ? (4 * values.length) : 0) + 2;
BinaryDataBuffer buffer = new BinaryDataBuffer(len);
buffer.putByte(DATA_TYPE_OFFSET, FLOAT_ARRAY);
if (values != null) {
int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0);
buffer.putByte(offset++, (byte) 0);
for (int i = 0; i < values.length; i++) {
offset = buffer.putInt(offset, Float.floatToIntBits(values[i]));
}
}
else {
buffer.putByte(DATA_OFFSET, (byte)-1);
buffer.putByte(DATA_OFFSET, (byte) -1);
}
data = buffer.getData();
}
/**
* Construct a coded field from a double array.
* @param values double array
*/
public BinaryCodedField(double[] values) {
int len = (values != null ? (8*values.length) : 0) + 2;
int len = (values != null ? (8 * values.length) : 0) + 2;
BinaryDataBuffer buffer = new BinaryDataBuffer(len);
buffer.putByte(DATA_TYPE_OFFSET, DOUBLE_ARRAY);
if (values != null) {
int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0);
buffer.putByte(offset++, (byte) 0);
for (int i = 0; i < values.length; i++) {
offset = buffer.putLong(offset, Double.doubleToLongBits(values[i]));
}
}
else {
buffer.putByte(DATA_OFFSET, (byte)-1);
buffer.putByte(DATA_OFFSET, (byte) -1);
}
data = buffer.getData();
}
/**
* Construct a coded field from a String array.
* @param strings String array
@@ -256,29 +256,31 @@ public class BinaryCodedField extends BinaryField {
}
buffer = new BinaryDataBuffer(len);
int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0);
buffer.putByte(offset++, (byte) 0);
try {
for (int i = 0; i < strings.length; i++) {
if (strings[i] == null) {
offset = buffer.putInt(offset, -1);
} else {
}
else {
byte[] bytes = strings[i].getBytes(STRING_ENCODING);
offset = buffer.putInt(offset, bytes.length);
offset = buffer.put(offset, bytes);
}
}
} catch (UnsupportedEncodingException e) {
}
catch (UnsupportedEncodingException e) {
throw new AssertException();
}
}
else {
buffer = new BinaryDataBuffer(2);
buffer.putByte(DATA_OFFSET, (byte)-1);
buffer.putByte(DATA_OFFSET, (byte) -1);
}
buffer.putByte(DATA_TYPE_OFFSET, STRING_ARRAY);
data = buffer.getData();
}
/**
* Get the data type associated with this field.
* @return data type
@@ -286,7 +288,7 @@ public class BinaryCodedField extends BinaryField {
public byte getDataType() {
return data[DATA_TYPE_OFFSET];
}
/**
* Get the double value contained with this field.
* @return double value
@@ -299,7 +301,7 @@ public class BinaryCodedField extends BinaryField {
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
return Double.longBitsToDouble(buffer.getLong(DATA_OFFSET));
}
/**
* Get the float value contained with this field.
* @return float value
@@ -312,7 +314,7 @@ public class BinaryCodedField extends BinaryField {
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
return Float.intBitsToFloat(buffer.getInt(DATA_OFFSET));
}
/**
* Get the byte array contained with this field.
* @return byte array
@@ -329,7 +331,7 @@ public class BinaryCodedField extends BinaryField {
System.arraycopy(data, 2, values, 0, values.length);
return values;
}
/**
* Get the short array contained with this field.
* @return short array
@@ -342,7 +344,7 @@ public class BinaryCodedField extends BinaryField {
if (data[DATA_OFFSET] < 0) {
return null;
}
short[] values = new short[(data.length -2) / 2];
short[] values = new short[(data.length - 2) / 2];
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
int offset = DATA_OFFSET + 1;
for (int i = 0; i < values.length; i++) {
@@ -351,7 +353,7 @@ public class BinaryCodedField extends BinaryField {
}
return values;
}
/**
* Get the int array contained with this field.
* @return int array
@@ -364,7 +366,7 @@ public class BinaryCodedField extends BinaryField {
if (data[DATA_OFFSET] < 0) {
return null;
}
int[] values = new int[(data.length -2) / 4];
int[] values = new int[(data.length - 2) / 4];
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
int offset = DATA_OFFSET + 1;
for (int i = 0; i < values.length; i++) {
@@ -373,7 +375,7 @@ public class BinaryCodedField extends BinaryField {
}
return values;
}
/**
* Get the long array contained with this field.
* @return long array
@@ -386,7 +388,7 @@ public class BinaryCodedField extends BinaryField {
if (data[DATA_OFFSET] < 0) {
return null;
}
long[] values = new long[(data.length -2) / 8];
long[] values = new long[(data.length - 2) / 8];
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
int offset = DATA_OFFSET + 1;
for (int i = 0; i < values.length; i++) {
@@ -395,7 +397,7 @@ public class BinaryCodedField extends BinaryField {
}
return values;
}
/**
* Get the float array contained with this field.
* @return float array
@@ -408,7 +410,7 @@ public class BinaryCodedField extends BinaryField {
if (data[DATA_OFFSET] < 0) {
return null;
}
float[] values = new float[(data.length -2) / 4];
float[] values = new float[(data.length - 2) / 4];
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
int offset = DATA_OFFSET + 1;
for (int i = 0; i < values.length; i++) {
@@ -417,7 +419,7 @@ public class BinaryCodedField extends BinaryField {
}
return values;
}
/**
* Get the double array contained with this field.
* @return double array
@@ -430,7 +432,7 @@ public class BinaryCodedField extends BinaryField {
if (data[DATA_OFFSET] < 0) {
return null;
}
double[] values = new double[(data.length -2) / 8];
double[] values = new double[(data.length - 2) / 8];
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
int offset = DATA_OFFSET + 1;
for (int i = 0; i < values.length; i++) {
@@ -439,7 +441,7 @@ public class BinaryCodedField extends BinaryField {
}
return values;
}
/**
* Get the String array contained with this field.
* @return String array
@@ -463,11 +465,13 @@ public class BinaryCodedField extends BinaryField {
byte[] bytes = buffer.get(offset, len);
strList.add(new String(bytes, STRING_ENCODING));
offset += len;
} else {
strList.add(null);
}
else {
strList.add(null);
}
}
} catch (UnsupportedEncodingException e) {
}
catch (UnsupportedEncodingException e) {
throw new AssertException();
}
String[] strings = new String[strList.size()];
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,13 +18,21 @@ package db;
import java.io.IOException;
import java.util.Arrays;
import db.buffers.DataBuffer;
/**
* <code>BinaryField</code> provides a wrapper for variable length binary data which is read or
* written to a Record.
*/
public class BinaryField extends Field {
/**
* Instance intended for defining a {@link Table} {@link Schema}
*/
public static final BinaryField INSTANCE = new BinaryField(null, true);
protected byte[] data;
private Integer hashcode;
/**
* Construct a binary data field with an initial value of null.
@@ -38,36 +45,41 @@ public class BinaryField extends Field {
* @param data initial value
*/
public BinaryField(byte[] data) {
this(data, false);
}
/**
* Construct a binary data field with an initial value of data.
* @param data initial value
* @param immutable true if field value is immutable
*/
BinaryField(byte[] data, boolean immutable) {
super(immutable);
this.data = data;
}
/*
* @see ghidra.framework.store.db.Field#getBinaryData()
*/
@Override
void checkImmutable() {
super.checkImmutable();
hashcode = null;
}
@Override
public byte[] getBinaryData() {
return data;
}
/*
* @see ghidra.framework.store.db.Field#setBinaryData(byte[])
*/
@Override
public void setBinaryData(byte[] data) {
checkImmutable();
this.data = data;
}
/*
* @see ghidra.framework.store.db.Field#length()
*/
@Override
int length() {
return (data == null) ? 4 : (data.length + 4);
}
/*
* @see ghidra.framework.store.db.Field#write(ghidra.framework.store.Buffer, int)
*/
@Override
int write(Buffer buf, int offset) throws IOException {
if (data == null) {
@@ -77,11 +89,9 @@ public class BinaryField extends Field {
return buf.put(offset, data);
}
/*
* @see ghidra.framework.store.db.Field#read(ghidra.framework.store.Buffer, int)
*/
@Override
int read(Buffer buf, int offset) throws IOException {
checkImmutable();
int len = buf.getInt(offset);
offset += 4;
if (len < 0) {
@@ -94,97 +104,25 @@ public class BinaryField extends Field {
return offset;
}
/*
* @see ghidra.framework.store.db.Field#readLength(ghidra.framework.store.Buffer, int)
*/
@Override
int readLength(Buffer buf, int offset) throws IOException {
int len = buf.getInt(offset);
return (len < 0 ? 0 : len) + 4;
}
/*
* @see ghidra.framework.store.db.Field#isVariableLength()
*/
@Override
public boolean isVariableLength() {
return true;
}
/*
* @see ghidra.framework.store.db.Field#getFieldType()
*/
@Override
protected byte getFieldType() {
byte getFieldType() {
return BINARY_OBJ_TYPE;
}
/*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
if (data == null) {
return "BinaryField: null";
}
return "BinaryField[" + data.length + "] = " + getValueAsString();
}
@Override
public String getValueAsString() {
StringBuffer buf = new StringBuffer();
int i = 0;
for (; i < 24 && i < data.length; i++) {
String b = Integer.toHexString(data[i] & 0xff);
if (b.length() == 1) {
buf.append('0');
}
buf.append(b);
buf.append(' ');
}
if (i < data.length) {
buf.append("...");
}
return buf.toString();
}
/*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof BinaryField))
return false;
BinaryField f = (BinaryField) obj;
return Arrays.equals(f.data, data);
}
// /**
// * Get first 8 bytes of data as long value.
// * First data byte corresponds to most significant byte
// * of long value so that proper sign is preserved.
// * If data is null, Long.MIN_VALUE is returned.
// * @see ghidra.framework.store.db.Field#getLongValue()
// */
// public long getLongValue() {
// long value = 0;
// if (data == null) {
// return Long.MIN_VALUE;
// }
// for (int i = 0; i < 8 && i < data.length; i++) {
// value = (value << 8) | ((long)data[i] & 0x000000ff);
// }
// if (data.length < 8) {
// value = value << (8 * (8 - data.length));
// }
// return value;
// }
/*
* @see ghidra.framework.store.db.Field#truncate(int)
*/
@Override
void truncate(int length) {
checkImmutable();
int maxLen = length - 4;
if (data != null && data.length > maxLen) {
byte[] newData = new byte[maxLen];
@@ -193,9 +131,6 @@ public class BinaryField extends Field {
}
}
/*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Field o) {
BinaryField f = (BinaryField) o;
@@ -224,28 +159,105 @@ public class BinaryField extends Field {
return len1 - len2;
}
/*
* @see ghidra.framework.store.db.Field#newField(ghidra.framework.store.db.Field)
*/
@Override
public Field newField(Field fieldValue) {
return new BinaryField(fieldValue.getBinaryData());
int compareTo(DataBuffer buffer, int offset) {
int len = buffer.getInt(offset);
if (data == null) {
if (len < 0) {
return 0;
}
return -1;
}
else if (len < 0) {
return 1;
}
return -buffer.unsignedCompareTo(data, offset + 4, len);
}
/*
* @see ghidra.framework.store.db.Field#newField()
*/
@Override
public Field newField() {
public BinaryField copyField() {
return new BinaryField(getBinaryData().clone());
}
@Override
public BinaryField newField() {
return new BinaryField();
}
/*
* @see java.lang.Object#hashCode()
*/
@Override
BinaryField getMinValue() {
throw new UnsupportedOperationException();
}
@Override
BinaryField getMaxValue() {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != getClass())
return false;
BinaryField f = (BinaryField) obj;
return Arrays.equals(f.data, data);
}
@Override
public int hashCode() {
return data.hashCode();
if (hashcode == null) {
int h = 0;
if (data != null) {
for (byte b : data) {
h = 31 * h + (b & 0xff);
}
}
hashcode = h;
}
return hashcode;
}
/// Methods below should not use data field directly
@Override
public String toString() {
String classname = getClass().getSimpleName();
byte[] d = getBinaryData();
if (d == null) {
return classname + ": null";
}
return classname = "[" + d.length + "] = 0x" + getValueAsString(d);
}
@Override
public String getValueAsString() {
byte[] d = getBinaryData();
if (d == null) {
return "null";
}
return "{" + getValueAsString(d) + "}";
}
/**
* Get format value string for byte array
* @param data byte array
* @return formatted value string
*/
public static String getValueAsString(byte[] data) {
StringBuffer buf = new StringBuffer();
int i = 0;
for (; i < 24 && i < data.length; i++) {
String b = Integer.toHexString(data[i] & 0xff);
if (b.length() == 1) {
buf.append('0');
}
buf.append(b);
buf.append(' ');
}
if (i < data.length) {
buf.append("...");
}
return buf.toString();
}
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,15 +15,30 @@
*/
package db;
import ghidra.util.exception.AssertException;
import java.io.IOException;
import db.buffers.DataBuffer;
/**
* <code>BooleanField</code> provides a wrapper for boolean data which is read or
* written to a Record.
*/
public class BooleanField extends Field {
public final class BooleanField extends Field {
/**
* Minimum boolean field value (FALSE)
*/
public static final BooleanField MIN_VALUE = new BooleanField(false, true);
/**
* Maximum boolean field value (TRUE)
*/
public static final BooleanField MAX_VALUE = new BooleanField(true, true);
/**
* Instance intended for defining a {@link Table} {@link Schema}
*/
public static final BooleanField INSTANCE = MIN_VALUE;
private byte value;
@@ -39,70 +53,57 @@ public class BooleanField extends Field {
* @param b initial value
*/
public BooleanField(boolean b) {
this(b, false);
}
/**
* Construct a boolean data field with an initial value of b.
* @param b initial value
* @param immutable true if field value is immutable
*/
BooleanField(boolean b, boolean immutable) {
super(immutable);
value = b ? (byte) 1 : (byte) 0;
}
/*
* @see ghidra.framework.store.db.Field#getBooleanValue()
*/
@Override
public boolean getBooleanValue() {
return (value == 0) ? false : true;
}
/*
* @see ghidra.framework.store.db.Field#setBooleanValue(boolean)
*/
@Override
public void setBooleanValue(boolean b) {
checkImmutable();
this.value = b ? (byte) 1 : (byte) 0;
}
/*
* @see ghidra.framework.store.db.Field#length()
*/
@Override
int length() {
return 1;
}
/*
* @see ghidra.framework.store.db.Field#write(ghidra.framework.store.Buffer, int)
*/
@Override
int write(Buffer buf, int offset) throws IOException {
return buf.putByte(offset, value);
}
/*
* @see ghidra.framework.store.db.Field#read(ghidra.framework.store.Buffer, int)
*/
@Override
int read(Buffer buf, int offset) throws IOException {
checkImmutable();
value = buf.getByte(offset);
return offset + 1;
}
/*
* @see ghidra.framework.store.db.Field#readLength(ghidra.framework.store.Buffer, int)
*/
@Override
int readLength(Buffer buf, int offset) throws IOException {
return 1;
}
/*
* @see ghidra.framework.store.db.Field#getFieldType()
*/
@Override
protected byte getFieldType() {
byte getFieldType() {
return BOOLEAN_TYPE;
}
/*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "BooleanField: " + Boolean.toString(getBooleanValue());
@@ -113,9 +114,6 @@ public class BooleanField extends Field {
return Boolean.toString(getBooleanValue());
}
/*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof BooleanField))
@@ -124,9 +122,6 @@ public class BooleanField extends Field {
return otherField.value == value;
}
/*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Field o) {
BooleanField f = (BooleanField) o;
@@ -137,44 +132,58 @@ public class BooleanField extends Field {
return 1;
}
/*
* @see ghidra.framework.store.db.Field#newField(ghidra.framework.store.db.Field)
*/
@Override
public Field newField(Field fieldValue) {
if (fieldValue.isVariableLength())
throw new AssertException();
return new BooleanField(fieldValue.getLongValue() != 0);
int compareTo(DataBuffer buffer, int offset) {
byte otherValue = buffer.getByte(offset);
if (value == otherValue)
return 0;
else if (value < otherValue)
return -1;
return 1;
}
/*
* @see ghidra.framework.store.db.Field#newField()
*/
@Override
public Field newField() {
public BooleanField copyField() {
return new BooleanField(getLongValue() != 0);
}
@Override
public BooleanField newField() {
return new BooleanField();
}
/*
* @see ghidra.framework.store.db.Field#getLongValue()
*/
@Override
public long getLongValue() {
return value;
}
/*
* @see ghidra.framework.store.db.Field#getBinaryData()
*/
@Override
public byte[] getBinaryData() {
return new byte[] { value };
}
@Override
public void setBinaryData(byte[] bytes) {
checkImmutable();
if (bytes.length != 1) {
throw new IllegalFieldAccessException();
}
value = bytes[0];
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return value;
}
@Override
BooleanField getMinValue() {
return MIN_VALUE;
}
@Override
BooleanField getMaxValue() {
return MAX_VALUE;
}
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,20 +22,20 @@ import java.io.IOException;
* providing various data access methods.
*/
public interface Buffer {
/**
* Get the buffer ID for this buffer.
* @return int
*/
public int getId();
/**
* Get the length of the buffer in bytes. The length reflects the number of
* bytes which have been allocated to the buffer.
* @return length of allocated buffer.
*/
public int length();
/**
* Get the byte data located at the specified offset and store into the
* bytes array provided.
@@ -62,10 +61,11 @@ public interface Buffer {
* underlying storage.
*/
public void get(int offset, byte[] data, int dataOffset, int length) throws IOException;
/**
* Get the byte data located at the specified offset.
* @param offset byte offset from start of buffer.
* @param length number of bytes to be read and returned
* @return the byte array.
* @throws ArrayIndexOutOfBoundsException is thrown if an invalid offset is
* specified or the end of the buffer was encountered while reading the
@@ -74,7 +74,7 @@ public interface Buffer {
* underlying storage.
*/
public byte[] get(int offset, int length) throws IOException;
/**
* Get the 8-bit byte value located at the specified offset.
* @param offset byte offset from start of buffer.
@@ -85,7 +85,7 @@ public interface Buffer {
* underlying storage.
*/
public byte getByte(int offset) throws IOException;
/**
* Get the 32-bit integer value located at the specified offset.
* @param offset byte offset from start of buffer.
@@ -97,7 +97,7 @@ public interface Buffer {
* underlying storage.
*/
public int getInt(int offset) throws IOException;
/**
* Get the 16-bit short value located at the specified offset.
* @param offset byte offset from start of buffer.
@@ -109,7 +109,7 @@ public interface Buffer {
* underlying storage.
*/
public short getShort(int offset) throws IOException;
/**
* Get the 64-bit long value located at the specified offset.
* @param offset byte offset from start of buffer.
@@ -121,7 +121,7 @@ public interface Buffer {
* underlying storage.
*/
public long getLong(int offset) throws IOException;
/**
* Put a specified number of bytes from the array provided into the buffer
* at the specified offset. The number of bytes stored is specified by the
@@ -153,7 +153,7 @@ public interface Buffer {
* underlying storage.
*/
public int put(int offset, byte[] bytes) throws IOException;
/**
* Put the 8-bit byte value into the buffer at the specified offset.
* @param offset byte offset from start of buffer.
@@ -165,7 +165,7 @@ public interface Buffer {
* underlying storage.
*/
public int putByte(int offset, byte b) throws IOException;
/**
* Put the 32-bit integer value into the buffer at the specified offset.
* @param offset byte offset from start of buffer.
@@ -178,7 +178,7 @@ public interface Buffer {
* underlying storage.
*/
public int putInt(int offset, int v) throws IOException;
/**
* Put the 16-bit short value into the buffer at the specified offset.
* @param offset byte offset from start of buffer.
@@ -191,7 +191,7 @@ public interface Buffer {
* underlying storage.
*/
public int putShort(int offset, short v) throws IOException;
/**
* Put the 64-bit long value into the buffer at the specified offset.
* @param offset byte offset from start of buffer.
@@ -204,5 +204,5 @@ public interface Buffer {
* underlying storage.
*/
public int putLong(int offset, long v) throws IOException;
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,15 +15,30 @@
*/
package db;
import ghidra.util.exception.AssertException;
import java.io.IOException;
import db.buffers.DataBuffer;
/**
* <code>ByteField</code> provides a wrapper for single signed byte data
* which is read or written to a Record.
*/
public class ByteField extends Field {
public final class ByteField extends Field {
/**
* Minimum byte field value
*/
public static final ByteField MIN_VALUE = new ByteField(Byte.MIN_VALUE, true);
/**
* Maximum byte field value
*/
public static final ByteField MAX_VALUE = new ByteField(Byte.MAX_VALUE, true);
/**
* Instance intended for defining a {@link Table} {@link Schema}
*/
public static final ByteField INSTANCE = MIN_VALUE;
private byte value;
@@ -39,69 +53,57 @@ public class ByteField extends Field {
* @param b initial value
*/
public ByteField(byte b) {
this(b, false);
}
/**
* Construct a byte field with an initial value of b.
* @param b initial value
* @param immutable true if field value is immutable
*/
ByteField(byte b, boolean immutable) {
super(immutable);
value = b;
}
/*
* @see ghidra.framework.store.db.Field#getByteValue()
*/
@Override
public byte getByteValue() {
return value;
}
/*
* @see ghidra.framework.store.db.Field#setByteValue(byte)
*/
@Override
public void setByteValue(byte value) {
checkImmutable();
this.value = value;
}
/*
* @see ghidra.framework.store.db.Field#length()
*/
@Override
int length() {
return 1;
}
/*
* @see ghidra.framework.store.db.Field#write(ghidra.framework.store.Buffer, int)
*/
@Override
int write(Buffer buf, int offset) throws IOException {
return buf.putByte(offset, value);
}
/*
* @see ghidra.framework.store.db.Field#read(ghidra.framework.store.Buffer, int)
*/
@Override
int read(Buffer buf, int offset) throws IOException {
checkImmutable();
value = buf.getByte(offset);
return offset + 1;
}
/*
* @see ghidra.framework.store.db.Field#readLength(ghidra.framework.store.Buffer, int)
*/
@Override
int readLength(Buffer buf, int offset) throws IOException {
return 1;
}
/*
* @see ghidra.framework.store.db.Field#getFieldType()
*/
@Override
protected byte getFieldType() {
byte getFieldType() {
return BYTE_TYPE;
}
/*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Byte: " + Byte.toString(value);
@@ -109,12 +111,9 @@ public class ByteField extends Field {
@Override
public String getValueAsString() {
return Integer.toHexString(value);
return "0x" + Integer.toHexString(value);
}
/*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof ByteField))
@@ -122,9 +121,6 @@ public class ByteField extends Field {
return ((ByteField) obj).value == value;
}
/*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Field o) {
ByteField f = (ByteField) o;
@@ -135,54 +131,63 @@ public class ByteField extends Field {
return 1;
}
/*
* @see ghidra.framework.store.db.Field#newField(ghidra.framework.store.db.Field)
*/
@Override
public Field newField(Field fieldValue) {
if (fieldValue.isVariableLength())
throw new AssertException();
return new ByteField((byte) fieldValue.getLongValue());
int compareTo(DataBuffer buffer, int offset) {
byte otherValue = buffer.getByte(offset);
if (value == otherValue)
return 0;
else if (value < otherValue)
return -1;
return 1;
}
/*
* @see ghidra.framework.store.db.Field#newField()
*/
@Override
public Field newField() {
public ByteField copyField() {
return new ByteField((byte) getLongValue());
}
@Override
public ByteField newField() {
return new ByteField();
}
/*
* @see ghidra.framework.store.db.Field#getLongValue()
*/
@Override
public long getLongValue() {
return value;
}
/*
* @see ghidra.framework.store.db.Field#setLongValue(long)
*/
@Override
public void setLongValue(long value) {
this.value = (byte) value;
setByteValue((byte) value);
}
/*
* @see ghidra.framework.store.db.Field#getBinaryData()
*/
@Override
public byte[] getBinaryData() {
return new byte[] { value };
}
/*
* @see java.lang.Object#hashCode()
*/
@Override
public void setBinaryData(byte[] bytes) {
checkImmutable();
if (bytes.length != 1) {
throw new IllegalFieldAccessException();
}
value = bytes[0];
}
@Override
public int hashCode() {
return value;
}
@Override
ByteField getMinValue() {
return MIN_VALUE;
}
@Override
ByteField getMaxValue() {
return MAX_VALUE;
}
}
@@ -123,7 +123,7 @@ public class ChainedBuffer implements Buffer {
* @param unintializedDataSourceOffset uninitialized data source offset which corresponds to
* this buffers contents.
* @param bufferMgr database buffer manager
* @throws IOException
* @throws IOException thrown if an IO error occurs
*/
public ChainedBuffer(int size, boolean enableObfuscation, Buffer uninitializedDataSource,
int unintializedDataSourceOffset, BufferMgr bufferMgr) throws IOException {
@@ -171,7 +171,7 @@ public class ChainedBuffer implements Buffer {
* @param size {@literal buffer size (0 < size <= 0x7fffffff)}
* @param enableObfuscation true to enable xor-ing of stored data to facilitate data obfuscation.
* @param bufferMgr database buffer manager
* @throws IOException
* @throws IOException thrown if an IO error occurs
*/
public ChainedBuffer(int size, boolean enableObfuscation, BufferMgr bufferMgr)
throws IOException {
@@ -183,7 +183,7 @@ public class ChainedBuffer implements Buffer {
* This method may only be invoked while a database transaction is in progress.
* @param size {@literal buffer size (0 < size <= 0x7fffffff)}
* @param bufferMgr database buffer manager
* @throws IOException
* @throws IOException thrown if an IO error occurs
*/
public ChainedBuffer(int size, BufferMgr bufferMgr) throws IOException {
this(size, false, null, 0, bufferMgr);
@@ -198,7 +198,7 @@ public class ChainedBuffer implements Buffer {
* This should not be specified if buffer will be completely filled/initialized.
* @param unintializedDataSourceOffset uninitialized data source offset which corresponds to
* this buffers contents.
* @throws IOException
* @throws IOException thrown if an IO error occurs
*/
public ChainedBuffer(BufferMgr bufferMgr, int bufferId, Buffer uninitializedDataSource,
int unintializedDataSourceOffset) throws IOException {
@@ -238,6 +238,7 @@ public class ChainedBuffer implements Buffer {
* Construct an existing chained buffer.
* @param bufferMgr database buffer manager
* @param bufferId database buffer ID which corresponds to a stored ChainedBuffer
* @throws IOException thrown if an IO error occurs
*/
public ChainedBuffer(BufferMgr bufferMgr, int bufferId) throws IOException {
this(bufferMgr, bufferId, null, 0);
@@ -249,12 +250,12 @@ public class ChainedBuffer implements Buffer {
}
/**
* Generate the XOR value for the specified byteValue which is located at the
* Generate the XOR'd value for the specified byteValue which is located at the
* specified bufferOffset.
* @param bufferOffset offset within a single chained buffer, valid values are in the
* range 0 to (dataSpace-1).
* @param byteValue
* @return
* range 0 to (dataSpace-1). This value is used to determine the appropriate XOR mask.
* @param byteValue value to be XOR'd against appropriate mask value
* @return XOR'd value
*/
private byte xorMaskByte(int bufferOffset, byte byteValue) {
byte maskByte = XOR_MASK_BYTES[bufferOffset % XOR_MASK_BYTES.length];
@@ -267,7 +268,7 @@ public class ChainedBuffer implements Buffer {
* @param bufferOffset offset within a single chained buffer, valid values are in the
* range 0 to (dataSpace-1). The value (bufferOffset+len-1) must be less than dataSpace.
* @param len mask length (2, 4, or 8)
* @return
* @return XOR mask of specified length which corresponds to specified bufferOffset.
*/
private long getXorMask(int bufferOffset, int len) {
long mask = 0;
@@ -284,8 +285,9 @@ public class ChainedBuffer implements Buffer {
* The same uninitialized read-only dataSource used for a chained buffer should be re-applied
* anytime this chained buffer is re-instantiated.
*
* @param dataSource
* @param dataSourceOffset
* @param dataSource data source for unitilized bytes
* @param dataSourceOffset offset within dataSource which corresponds to first byte of
* this chained buffer.
*/
private void setUnintializedDataSource(Buffer dataSource, int dataSourceOffset) {
@@ -321,6 +323,7 @@ public class ChainedBuffer implements Buffer {
/**
* Return the maximum number of buffers consumed by the storage of this DBBuffer object.
* The actual number may be less if data has not been written to the entire buffer.
* @return total number of buffers consumed by this ChaninedBuffer.
*/
int getBufferCount() {
return dataBufferIdTable.length +
@@ -734,7 +737,7 @@ public class ChainedBuffer implements Buffer {
* The index buffer provided is always released.
* @param indexBuffer the last index buffer.
* @return DataBuffer
* @throws IOException
* @throws IOException thrown if an IO error occurs
*/
private DataBuffer appendIndexBuffer(DataBuffer indexBuffer) throws IOException {
try {
@@ -856,6 +859,7 @@ public class ChainedBuffer implements Buffer {
/**
* Delete and release all underlying DataBuffers.
* @throws IOException thrown if an IO error occurs
*/
public synchronized void delete() throws IOException {
if (readOnly) {
@@ -1115,6 +1119,7 @@ public class ChainedBuffer implements Buffer {
* @param startOffset starting offset, inclusive
* @param endOffset ending offset, exclusive
* @param fillByte byte value
* @throws IOException thrown if an IO error occurs
*/
public synchronized void fill(int startOffset, int endOffset, byte fillByte)
throws IOException {
@@ -1160,7 +1165,7 @@ public class ChainedBuffer implements Buffer {
* @return int actual number of bytes written.
* This could be smaller than length if the end of buffer is
* encountered while writing data.
* @throws IOException
* @throws IOException thrown if an IO error occurs
*/
private int putBytes(int index, int bufferDataOffset, byte[] data, int dataOffset, int length)
throws IOException {
@@ -1370,9 +1375,6 @@ public class ChainedBuffer implements Buffer {
return offset + 8;
}
/*
* @see ghidra.framework.store.Buffer#putShort(int, short)
*/
@Override
public synchronized int putShort(int offset, short v) throws IOException {
if (readOnly) {
@@ -1406,7 +1408,7 @@ public class ChainedBuffer implements Buffer {
* Get a data buffer.
* @param index index of within buffer chain
* @return requested data buffer.
* @throws IOException
* @throws IOException thrown if an IO error occurs
*/
private DataBuffer getBuffer(int index) throws IOException {
// if databufferIdTable is null, index must be null. let it throw null pointer in this case.
@@ -1425,7 +1427,7 @@ public class ChainedBuffer implements Buffer {
* Initialize specified DataBuffer which corresponds to the chain index.
* @param chainBufferIndex chain buffer index
* @param buf newly allocated database buffer
* @throws IOException
* @throws IOException thrown if an IO error occurs
*/
private void initializeAllocatedBuffer(int chainBufferIndex, DataBuffer buf)
throws IOException {
@@ -1455,7 +1457,7 @@ public class ChainedBuffer implements Buffer {
* Add a new data buffer as an indexed buffer.
* @param index buffer index.
* @param buf new data buffer.
* @throws IOException
* @throws IOException thrown if an IO error occurs
*/
private void addBuffer(int index, DataBuffer buf) throws IOException {
buf.putByte(NODE_TYPE_OFFSET, NodeMgr.CHAINED_BUFFER_DATA_NODE);
@@ -1,239 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import ghidra.util.LongIterator;
import java.io.IOException;
import java.util.NoSuchElementException;
/**
* <code>DBFieldMap</code> provides a database-backed map of non-unique Field values to long values.
*/
public class DBFieldMap {
private static final Class<?>[] fieldClasses = {
};
private static final String[] fieldNames = {
};
private static final int BUFFER_SIZE = 16 * 1024;
private DBHandle dbh;
private Schema schema;
private Table indexTable;
private Class<? extends Field> fieldClass;
/**
* Construct a new map.
* A temporary database is used to provide storage for the map.
* @param fieldClass specifies class of Field values to be stored in this map.
* @param cacheSizeMB size of data cache in MBytes.
*/
public DBFieldMap(Class<? extends Field> fieldClass, int cacheSizeMB) {
if (!Field.class.isAssignableFrom(fieldClass)) {
throw new IllegalArgumentException("Field class expected");
}
this.fieldClass = fieldClass;
int indexFieldType;
try {
indexFieldType = Field.INDEX_TYPE_FLAG |
fieldClass.newInstance().getFieldType();
} catch (Exception e) {
throw new IllegalArgumentException("Bad Field class: " + e.getMessage());
}
Field indexKeyField = IndexField.getIndexField((byte)indexFieldType);
schema = new Schema(0, indexKeyField.getClass(), "MapKey", fieldClasses, fieldNames);
boolean success = false;
try {
dbh = new DBHandle(BUFFER_SIZE, cacheSizeMB * 1024 * 1024);
long txId = dbh.startTransaction();
indexTable = dbh.createTable("DBFieldMap", schema);
dbh.endTransaction(txId, true);
success = true;
}
catch (IOException e) {
throw new RuntimeException(e);
}
finally {
if (!success && dbh != null) {
dbh.close();
dbh = null;
}
}
}
/**
* Dispose all resources associated with this map.
* This method should be invoked when the map is no longer needed.
*/
public void dispose() {
if (dbh != null) {
dbh.close();
dbh = null;
}
}
/*
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() throws Throwable {
dispose();
}
/**
* Add the specified value pair to this map.
* If the entry already exists, this method has no affect.
* @param fieldValue
* @param longValue
*/
public void addEntry(Field fieldValue, long longValue) {
if (!fieldClass.isInstance(fieldValue)) {
throw new IllegalArgumentException("Instance of " + fieldClass.getName() + " expected");
}
IndexField indexField = IndexField.getIndexField(fieldValue, longValue);
Record rec = schema.createRecord(indexField);
try {
long txId = dbh.startTransaction();
indexTable.putRecord(rec);
dbh.endTransaction(txId, true);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
}
/**
* Delete the specified value pair from this map.
* @param fieldValue
* @param longValue
* @return true if entry exists and was deleted
*/
public boolean deleteEntry(Field fieldValue, long longValue) {
if (!fieldClass.isInstance(fieldValue)) {
throw new IllegalArgumentException("Instance of " + fieldClass.getName() + " expected");
}
IndexField indexField = IndexField.getIndexField(fieldValue, longValue);
try {
long txId = dbh.startTransaction();
boolean success = indexTable.deleteRecord(indexField);
dbh.endTransaction(txId, true);
return success;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Determine if the specified value pair exists within this map.
* (This method provided for test purposes).
* @param fieldValue
* @param longValue
* @return
*/
boolean hasEntry(Field fieldValue, long longValue) {
if (!fieldClass.isInstance(fieldValue)) {
throw new IllegalArgumentException("Instance of " + fieldClass.getName() + " expected");
}
IndexField indexField = IndexField.getIndexField(fieldValue, longValue);
try {
return indexTable.hasRecord(indexField);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public LongIterator iterator() {
return new MapLongIterator();
}
private class MapLongIterator implements LongIterator {
DBFieldIterator indexIterator;
MapLongIterator() {
try {
indexIterator = indexTable.fieldKeyIterator();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/*
* @see ghidra.util.LongIterator#hasNext()
*/
public boolean hasNext() {
try {
return indexIterator.hasNext();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/*
* @see ghidra.util.LongIterator#next()
*/
public long next() {
try {
IndexField indexField = (IndexField) indexIterator.next();
if (indexField == null) {
throw new NoSuchElementException();
}
return indexField.getPrimaryKey();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/*
* @see ghidra.util.LongIterator#hasPrevious()
*/
public boolean hasPrevious() {
try {
return indexIterator.hasPrevious();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/*
* @see ghidra.util.LongIterator#previous()
*/
public long previous() {
try {
IndexField indexField = (IndexField) indexIterator.previous();
if (indexField == null) {
throw new NoSuchElementException();
}
return indexField.getPrimaryKey();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
+174 -32
View File
@@ -17,11 +17,24 @@ package db;
import java.io.IOException;
import db.buffers.DataBuffer;
/**
* <code>Field</code> is an abstract data wrapper for use with Records.
* Note that when comparing two Field instances both must be of the same
* class.
*/
public abstract class Field implements Comparable<Field> {
public static final Field[] EMPTY_ARRAY = new Field[0];
/**
* 8-bit Field Type Encoding (PPPPFFFF)
* where:
* FFFF - normal/indexed field type
* PPPP - indexed table primary key type (1000b indicates LegacyIndexField)
*/
/**
* Field type for ByteField
* @see db.ByteField
@@ -65,19 +78,56 @@ public abstract class Field implements Comparable<Field> {
static final byte BOOLEAN_TYPE = 6;
/**
* Field type flag mask used to isolate flag bits
* Field type for 10-byte binary FixedField(10)
* @see db.FixedField
*/
static final byte TYPE_FLAG_MASK = (byte) 0xC0;
static final byte FIXED_10_TYPE = 7;
/**
* Field base type mask used to isolate base type
* Legacy Index Primary Key Field type for LongField
* which was previously a boolean indicator for an index
* field with assumed long primary key.
* (see {@link LegacyIndexField})
*/
static final byte BASE_TYPE_MASK = (byte) 0x3F;
static final byte LEGACY_INDEX_LONG_TYPE = 8;
/**
* Field type flag bit shared by all Index type fields
* Field base type mask
*/
static final byte INDEX_TYPE_FLAG = (byte) 0x80;
static final byte FIELD_TYPE_MASK = (byte) 0x0F;
/**
* Field index primary key type mask
*/
static final byte INDEX_PRIMARY_KEY_TYPE_MASK = (byte) ~FIELD_TYPE_MASK;
/**
* Index Primary Key Field Type Shift
*/
static final int INDEX_FIELD_TYPE_SHIFT = 4;
private final boolean immutable;
/**
* Abstract Field Constructor for a mutable instance
*/
Field() {
immutable = false;
}
/**
* Abstract Field Constructor
* @param immutable true if field value is immutable
*/
Field(boolean immutable) {
this.immutable = immutable;
}
void checkImmutable() {
if (immutable) {
throw new IllegalFieldAccessException("immutable field instance");
}
}
/**
* Get field as a long value.
@@ -191,10 +241,11 @@ public abstract class Field implements Comparable<Field> {
* Set data from binary byte array.
* All variable-length fields must implement this method.
* @param bytes field data
* @throws IllegalFieldAccessException if error occurs while reading bytes
* into field which will generally be caused by the incorrect number of
* bytes provided to a fixed-length field.
*/
public void setBinaryData(byte[] bytes) {
throw new IllegalFieldAccessException();
}
abstract public void setBinaryData(byte[] bytes);
/**
* Get field as a String value.
@@ -219,10 +270,10 @@ public abstract class Field implements Comparable<Field> {
/**
* Truncate a variable length field to the specified length.
* If current length is shorterm, this method has no affect.
* @param length
* @param length truncated length
*/
void truncate(int length) {
throw new IllegalFieldAccessException();
throw new UnsupportedOperationException("Field may not be truncated");
}
/**
@@ -233,22 +284,31 @@ public abstract class Field implements Comparable<Field> {
}
/**
* Create new instance of this field type.
* @param fieldValue initial field value.
* @return long
* Determine if specified field is same type as this field
* @param field a Field instance
* @return true if field is same type as this field
*/
public abstract Field newField(Field fieldValue);
public boolean isSameType(Field field) {
return field != null && field.getClass() == getClass();
}
/**
* Create new instance of this field with the same value.
* @return new field instance with same value
*/
public abstract Field copyField();
/**
* Create new instance of this field type.
* @return long
* @return new field instance with undefined initial value
*/
public abstract Field newField();
/**
* Return Field instance type as an integer value
* Return Field instance type as an integer value.
* @return encoded field type
*/
protected abstract byte getFieldType();
abstract byte getFieldType();
/**
* Write the field to buf at the specified offset. When writing variable length
@@ -292,40 +352,73 @@ public abstract class Field implements Comparable<Field> {
*/
abstract int length();
/*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public abstract boolean equals(Object obj);
@Override
public abstract int hashCode();
/**
* Get field value as a formatted string
* @return field value string
*/
public abstract String getValueAsString();
/**
* Get minimum field value.
*
* Supported for fixed-length fields only.
* @return minimum value
* @throws UnsupportedOperationException if field is not fixed-length
*/
abstract Field getMinValue();
/**
* Get maximum field value.
*
* Supported for fixed-length fields only.
* @return maximum value
* @throws UnsupportedOperationException if field is not fixed-length
*/
abstract Field getMaxValue();
/**
* Performs a fast in-place comparison of this field value with another
* field value stored within the specified buffer at the the specified offset.
* @param buffer data buffer
* @param offset field value offset within buffer
* @return comparison value, zero if equal, -1 if this field has a value
* less than the stored field, or +1 if this field has a value greater than
* the stored field located at keyIndex.
*/
abstract int compareTo(DataBuffer buffer, int offset);
/**
* Get the field associated with the specified type value.
* @param fieldType encoded Field type
* @return Field
* @param fieldType field type index
* @return Field field instance which corresponds to the specified fieldType
* @throws UnsupportedFieldException if unsupported fieldType specified
*/
static Field getField(byte fieldType) throws UnsupportedFieldException {
if ((fieldType & INDEX_TYPE_FLAG) == 0) {
switch (fieldType & BASE_TYPE_MASK) {
if ((fieldType & INDEX_PRIMARY_KEY_TYPE_MASK) == 0) {
switch (fieldType & FIELD_TYPE_MASK) {
case LONG_TYPE:
return new LongField();
return LongField.INSTANCE;
case INT_TYPE:
return new IntField();
return IntField.INSTANCE;
case STRING_TYPE:
return new StringField();
return StringField.INSTANCE;
case SHORT_TYPE:
return new ShortField();
return ShortField.INSTANCE;
case BYTE_TYPE:
return new ByteField();
return ByteField.INSTANCE;
case BOOLEAN_TYPE:
return new BooleanField();
return BooleanField.INSTANCE;
case BINARY_OBJ_TYPE:
return new BinaryField();
return BinaryField.INSTANCE;
case FIXED_10_TYPE:
return FixedField10.INSTANCE;
}
}
else {
@@ -340,4 +433,53 @@ public abstract class Field implements Comparable<Field> {
}
}
/**
* Get the type index value of the FixedField type which corresponds
* to the specified fixed-length;
* @param fixedLength fixed length
* @return FixedLength field type index
*/
static byte getFixedType(int fixedLength) {
if (fixedLength == 10) {
return FIXED_10_TYPE;
}
throw new IllegalArgumentException(
"Unsupported fixed-length binary type size: " + fixedLength);
}
/**
* Get a fixed-length field of the specified size
* @param size fixed-field length (supported sizes: 1, 4, 8, 10)
* @return fixed field instance
* @throws IllegalArgumentException if unsupported fixed field length
*/
static Field getFixedField(int size) {
switch (size) {
case 1:
return new ByteField();
case 4:
return new IntField();
case 8:
return new LongField();
case 10:
return new FixedField10();
}
throw new IllegalArgumentException("Unsupported fixed-field length: " + size);
}
/**
* Determine if a specified field instance may be indexed
* @param field field to be checked
* @return true if field can be indexed
*/
public static boolean canIndex(Field field) {
if (field == null) {
return false;
}
if (field instanceof IndexField) {
return false;
}
return !field.isSameType(BooleanField.INSTANCE) && !field.isSameType(ByteField.INSTANCE);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import java.io.IOException;
/**
* <code>FieldKeyInteriorNode</code> defines a common interface for {@link FieldKeyNode}
* implementations which are also an {@link InteriorNode}.
*/
public interface FieldKeyInteriorNode extends InteriorNode, FieldKeyNode {
/**
* Callback method for when a child node's leftmost key changes.
* @param oldKey previous leftmost key.
* @param newKey new leftmost key.
* @param childNode child node containing oldKey (null if not a VarKeyNode)
* @throws IOException if IO error occurs
*/
void keyChanged(Field oldKey, Field newKey, FieldKeyNode childNode) throws IOException;
}
@@ -0,0 +1,65 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import java.io.IOException;
/**
* <code>FieldKeyNode</code> defines a common interface for {@link BTreeNode}
* implementations which utilize a {@link Field} key.
*/
interface FieldKeyNode extends BTreeNode {
/**
* @return the parent node or null if this is the root
*/
@Override
public FieldKeyInteriorNode getParent();
/**
* Get the leaf node which contains the specified key.
* @param key key value
* @return leaf node
* @throws IOException thrown if an IO error occurs
*/
public FieldKeyRecordNode getLeafNode(Field key) throws IOException;
/**
* Get the left-most leaf node within the tree.
* @return left-most leaf node.
* @throws IOException thrown if IO error occurs
*/
abstract FieldKeyRecordNode getLeftmostLeafNode() throws IOException;
/**
* Get the right-most leaf node within the tree.
* @return right-most leaf node.
* @throws IOException thrown if IO error occurs
*/
abstract FieldKeyRecordNode getRightmostLeafNode() throws IOException;
/**
* Performs a fast in-place key comparison of the specified key
* value with a key stored within this node at the specified keyIndex.
* @param k key value to be compared
* @param keyIndex key index to another key within this node's buffer
* @return comparison value, zero if equal, -1 if k has a value less than
* the store key, or +1 if k has a value greater than the stored key located
* at keyIndex.
*/
abstract int compareKeyField(Field k, int keyIndex);
}
@@ -0,0 +1,145 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import java.io.IOException;
/**
* <code>FieldKeyRecordNode</code> defines a common interface for {@link FieldKeyNode}
* implementations which are also a {@link RecordNode} (i.e., leaf node).
*/
interface FieldKeyRecordNode extends RecordNode, FieldKeyNode {
/**
* Get the record located at the specified index.
* @param schema record data schema
* @param index key index
* @return Record
* @throws IOException thrown if IO error occurs
*/
Record getRecord(Schema schema, int index) throws IOException;
/**
* Insert or Update a record.
* @param record data record with long key
* @param table table which will be notified when record is inserted or updated.
* @return root node which may have changed.
* @throws IOException thrown if IO error occurs
*/
FieldKeyNode putRecord(Record record, Table table) throws IOException;
/**
* Remove the record identified by index.
* This will never be the last record within the node.
* @param index record index
* @throws IOException thrown if IO error occurs
*/
void remove(int index) throws IOException;
/**
* Determine if this record node has a right sibling.
* @return true if right sibling exists
* @throws IOException if IO error occurs
*/
boolean hasNextLeaf() throws IOException;
/**
* Get this leaf node's right sibling
* @return this leaf node's right sibling or null if right sibling does not exist.
* @throws IOException if an IO error occurs
*/
FieldKeyRecordNode getNextLeaf() throws IOException;
/**
* Determine if this record node has a left sibling.
* @return true if left sibling exists
* @throws IOException if IO error occurs
*/
boolean hasPreviousLeaf() throws IOException;
/**
* Get this leaf node's left sibling
* @return this leaf node's left sibling or null if left sibling does not exist.
* @throws IOException if an IO error occurs
*/
FieldKeyRecordNode getPreviousLeaf() throws IOException;
/**
* Remove this leaf from the tree.
* @return root node which may have changed.
* @throws IOException thrown if IO error occurs
*/
FieldKeyNode removeLeaf() throws IOException;
/**
* Delete the record identified by the specified key.
* @param key record key
* @param table table which will be notified when record is deleted.
* @return root node which may have changed.
* @throws IOException thrown if IO error occurs
*/
FieldKeyNode deleteRecord(Field key, Table table) throws IOException;
/**
* Get the record with the minimum key value which is greater than or equal
* to the specified key.
* @param key search key
* @param schema record data schema
* @return Record requested or null if record not found.
* @throws IOException thrown if IO error occurs
*/
Record getRecordAtOrAfter(Field key, Schema schema) throws IOException;
/**
* Get the record with the maximum key value which is less than or equal
* to the specified key.
* @param key search key
* @param schema record data schema
* @return Record requested or null if record not found.
* @throws IOException thrown if IO error occurs
*/
Record getRecordAtOrBefore(Field key, Schema schema) throws IOException;
/**
* Get the record with the minimum key value which is greater than
* the specified key.
* @param key search key
* @param schema record data schema
* @return Record requested or null if record not found.
* @throws IOException thrown if IO error occurs
*/
Record getRecordAfter(Field key, Schema schema) throws IOException;
/**
* Get the record with the maximum key value which is less than
* the specified key.
* @param key search key
* @param schema record data schema
* @return Record requested or null if record not found.
* @throws IOException thrown if IO error occurs
*/
Record getRecordBefore(Field key, Schema schema) throws IOException;
/**
* Get the record identified by the specified key.
* @param key search key
* @param schema record data schema
* @return Record requested or null if record not found.
* @throws IOException thrown if IO error occurs
*/
Record getRecord(Field key, Schema schema) throws IOException;
}
@@ -0,0 +1,55 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
/**
* <code>FixedField</code> provides an abstract implementation of a fixed-length
* binary field.
*/
public abstract class FixedField extends BinaryField {
/**
* Construct a fixed-length field
* @param data initial value
* @param immutable true if field value is immutable
*/
FixedField(byte[] data, boolean immutable) {
super(data, immutable);
}
@Override
public final boolean isVariableLength() {
return false;
}
@Override
void truncate(int length) {
throw new UnsupportedOperationException("Field may not be truncated");
}
@Override
public abstract FixedField copyField();
@Override
public abstract FixedField newField();
@Override
abstract FixedField getMinValue();
@Override
abstract FixedField getMaxValue();
}
@@ -0,0 +1,215 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import java.io.IOException;
import db.buffers.DataBuffer;
import generic.util.UnsignedDataUtils;
import ghidra.util.BigEndianDataConverter;
/**
* <code>FixedField10</code> is a 10-byte fixed-length binary field.
*/
public class FixedField10 extends FixedField {
/**
* Minimum long field value
*/
public static FixedField10 MIN_VALUE = new FixedField10(0L, (short) 0, true);
/**
* Maximum long field value
*/
public static FixedField10 MAX_VALUE = new FixedField10(-1L, (short) -1, true);
/**
* Instance intended for defining a {@link Table} {@link Schema}
*/
@SuppressWarnings("hiding")
public static final FixedField10 INSTANCE = MIN_VALUE;
// This implementation uses both a data byte array and short+long variables
// for data storage. While the short+long is always available, the data
// byte array is only set when needed or supplied during construction.
// The use of the short+long is done to speed-up comparison with other
// FixedField10 instances or directly from a DataBuffer.
private short lo2;
private long hi8;
/**
* Construct a 10-byte fixed-length field with an initial value of 0.
*/
public FixedField10() {
super(null, false);
}
/**
* Construct a 10-byte fixed-length field with an initial value of data.
* @param data initial 10-byte binary value
* @throws IllegalArgumentException thrown if data is not 10-bytes in length
*/
public FixedField10(byte[] data) {
this(data, false);
}
/**
* Construct a 10-byte fixed-length binary field with an initial value of data.
* @param data initial 10-byte binary value
* @param immutable true if field value is immutable
* @throws IllegalArgumentException thrown if data is not 10-bytes in length
*/
public FixedField10(byte[] data, boolean immutable) {
super(null, immutable);
setBinaryData(data);
}
FixedField10(long hi8, short lo2, boolean immutable) {
super(null, immutable);
this.hi8 = hi8;
this.lo2 = lo2;
}
@Override
public int compareTo(Field o) {
if (!(o instanceof FixedField10)) {
throw new UnsupportedOperationException("may only compare similar Field types");
}
FixedField10 f = (FixedField10) o;
if (hi8 != f.hi8) {
return UnsignedDataUtils.unsignedLessThan(hi8, f.hi8) ? -1 : 1;
}
if (lo2 != f.lo2) {
return UnsignedDataUtils.unsignedLessThan(lo2, f.lo2) ? -1 : 1;
}
return 0;
}
@Override
int compareTo(DataBuffer buffer, int offset) {
long otherHi8 = buffer.getLong(offset);
if (hi8 != otherHi8) {
return UnsignedDataUtils.unsignedLessThan(hi8, otherHi8) ? -1 : 1;
}
short otherLo2 = buffer.getShort(offset + 8);
if (lo2 != otherLo2) {
return UnsignedDataUtils.unsignedLessThan(lo2, otherLo2) ? -1 : 1;
}
return 0;
}
@Override
public FixedField copyField() {
return new FixedField10(hi8, lo2, false);
}
@Override
public FixedField newField() {
return new FixedField10();
}
@Override
FixedField getMinValue() {
return MIN_VALUE;
}
@Override
FixedField getMaxValue() {
return MAX_VALUE;
}
@Override
public byte[] getBinaryData() {
if (data != null) {
return data;
}
data = new byte[10];
BigEndianDataConverter.INSTANCE.putLong(data, 0, hi8);
BigEndianDataConverter.INSTANCE.putShort(data, 8, lo2);
return data;
}
@Override
public void setBinaryData(byte[] data) {
if (data.length != 10) {
throw new IllegalArgumentException("Invalid FixedField10 length: " + data.length);
}
this.data = data;
hi8 = BigEndianDataConverter.INSTANCE.getLong(data, 0);
lo2 = BigEndianDataConverter.INSTANCE.getShort(data, 8);
}
@Override
byte getFieldType() {
return FIXED_10_TYPE;
}
@Override
int write(Buffer buf, int offset) throws IOException {
if (data != null) {
return buf.put(offset, data);
}
offset = buf.putLong(offset, hi8);
return buf.putShort(offset, lo2);
}
@Override
int read(Buffer buf, int offset) throws IOException {
checkImmutable();
data = null; // be lazy
hi8 = buf.getLong(offset);
lo2 = buf.getShort(offset + 8);
return offset + 10;
}
@Override
int readLength(Buffer buf, int offset) throws IOException {
return 10;
}
@Override
int length() {
return 10;
}
@Override
public int hashCode() {
final int prime = 31;
int result = (int) (hi8 ^ (hi8 >>> 32));
result = prime * result + lo2;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
FixedField10 other = (FixedField10) obj;
if (hi8 != other.hi8)
return false;
if (lo2 != other.lo2)
return false;
return true;
}
@Override
public String getValueAsString() {
return "{" + BinaryField.getValueAsString(getBinaryData()) + "}";
}
}
@@ -1,328 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import java.io.IOException;
import java.util.NoSuchElementException;
/**
* The <code>FixedIndexTable</code> provides a secondary index on a fixed-length table column
* (e.g., IntField, LongField, etc.). For each unique secondary index value, an IndexBuffer is
* stored within an underlying index Table record. The secondary index value is used as the long
* key to access this record. Within a single IndexBuffer is stored all primary keys which
* correspond to an index value.
*/
class FixedIndexTable extends IndexTable {
private static final Class<?>[] fieldClasses = { BinaryField.class, // index data
};
private static final String[] fieldNames = { "IndexBuffer" };
private static Schema indexSchema = new Schema(0, "IndexKey", fieldClasses, fieldNames);
/**
* Construct a new secondary index which is based upon a field within the
* primary table specified by name.
* @param primaryTable primary table.
* @param colIndex identifies the indexed column within the primary table.
* @throws IOException thrown if an IO error occurs
*/
FixedIndexTable(Table primaryTable, int colIndex) throws IOException {
this(primaryTable, primaryTable.getDBHandle().getMasterTable().createTableRecord(
primaryTable.getName(), indexSchema, colIndex));
}
/**
* Construct a new or existing secondary index. An existing index must have
* its root ID specified within the tableRecord.
* @param primaryTable primary table.
* @param indexTableRecord specifies the index parameters.
* @throws IOException thrown if an IO error occurs
*/
FixedIndexTable(Table primaryTable, TableRecord indexTableRecord) throws IOException {
super(primaryTable, indexTableRecord);
}
/**
* Find all primary keys which correspond to the specified indexed field
* value.
* @param indexValue the field value to search for.
* @return list of primary keys
* @throws IOException thrown if an IO error occurs
*/
@Override
long[] findPrimaryKeys(Field indexValue) throws IOException {
if (!indexValue.getClass().equals(fieldType.getClass())) {
throw new IllegalArgumentException("Incorrect indexed field type");
}
Record indexRecord = indexTable.getRecord(indexValue.getLongValue());
if (indexRecord == null) {
return emptyKeyArray;
}
IndexBuffer indexBuffer = new IndexBuffer(indexValue, indexRecord.getBinaryData(0));
return indexBuffer.getPrimaryKeys();
}
/**
* Get the number of primary keys which correspond to the specified indexed field
* value.
* @param indexValue the field value to search for.
* @return key count
*/
@Override
int getKeyCount(Field indexValue) throws IOException {
if (!indexValue.getClass().equals(fieldType.getClass())) {
throw new IllegalArgumentException("Incorrect indexed field type");
}
Record indexRecord = indexTable.getRecord(indexValue.getLongValue());
if (indexRecord == null) {
return 0;
}
IndexBuffer indexBuffer = new IndexBuffer(indexValue, indexRecord.getBinaryData(0));
return indexBuffer.keyCount;
}
/*
* @see ghidra.framework.store.db.IndexTable#addEntry(ghidra.framework.store.db.Record)
*/
@Override
void addEntry(Record record) throws IOException {
Field indexField = record.getField(colIndex);
long secondaryKey = indexField.getLongValue();
Record indexRecord = indexTable.getRecord(secondaryKey);
if (indexRecord == null) {
indexRecord = indexSchema.createRecord(secondaryKey);
}
IndexBuffer indexBuffer = new IndexBuffer(indexField, indexRecord.getBinaryData(0));
indexBuffer.addEntry(record.getKey());
indexRecord.setBinaryData(0, indexBuffer.getData());
indexTable.putRecord(indexRecord);
}
/*
* @see ghidra.framework.store.db.IndexTable#deleteEntry(ghidra.framework.store.db.Record)
*/
@Override
void deleteEntry(Record record) throws IOException {
Field indexField = record.getField(colIndex);
long secondaryKey = indexField.getLongValue();
Record indexRecord = indexTable.getRecord(secondaryKey);
if (indexRecord != null) {
IndexBuffer indexBuffer = new IndexBuffer(indexField, indexRecord.getBinaryData(0));
indexBuffer.deleteEntry(record.getKey());
byte[] data = indexBuffer.getData();
if (data == null) {
indexTable.deleteRecord(secondaryKey);
}
else {
indexRecord.setBinaryData(0, data);
indexTable.putRecord(indexRecord);
}
}
}
/**
* Get the index buffer associated with the specified index key
* @param indexKey index key
* @return index buffer or null if not found
* @throws IOException thrown if IO error occurs
*/
private IndexBuffer getIndexBuffer(Field indexKey) throws IOException {
Record indexRec = indexTable.getRecord(indexKey.getLongValue());
return indexRec != null ? new IndexBuffer(indexKey, indexRec.getBinaryData(0)) : null;
}
/*
* @see ghidra.framework.store.db.IndexTable#indexIterator()
*/
@Override
DBFieldIterator indexIterator() throws IOException {
return new IndexLongIterator();
}
/*
* @see ghidra.framework.store.db.IndexTable#indexIterator(ghidra.framework.store.db.Field, ghidra.framework.store.db.Field, boolean)
*/
@Override
DBFieldIterator indexIterator(Field minField, Field maxField, boolean atMin)
throws IOException {
long min = minField != null ? minField.getLongValue() : Long.MIN_VALUE;
long max = maxField != null ? maxField.getLongValue() : Long.MAX_VALUE;
return new IndexLongIterator(min, max, atMin);
}
/*
* @see db.IndexTable#indexIterator(db.Field, db.Field, db.Field, boolean)
*/
@Override
DBFieldIterator indexIterator(Field minField, Field maxField, Field startField, boolean before)
throws IOException {
if (startField == null) {
throw new IllegalArgumentException("starting index value required");
}
long min = minField != null ? minField.getLongValue() : Long.MIN_VALUE;
long max = maxField != null ? maxField.getLongValue() : Long.MAX_VALUE;
return new IndexLongIterator(min, max, startField.getLongValue(), before);
}
/**
* Iterates over index field values within a specified range.
*/
class IndexLongIterator implements DBFieldIterator {
private Field lastKey;
private Field keyField;
private DBLongIterator indexIterator;
private boolean hasNext = false;
private boolean hasPrev = false;
/**
* Construct an index field iterator starting with the minimum index value.
*/
IndexLongIterator() throws IOException {
indexIterator = indexTable.longKeyIterator();
}
/**
* Construct an index field iterator. The iterator is positioned at index
* value identified by startValue.
* @param startValue minimum index value or null if no minimum
* @param endValue maximum index value or null if no maximum
* @param atStart if true initial position is before startValue, else position
* is after endValue
* @throws IOException
*/
IndexLongIterator(long minValue, long maxValue, boolean atMin) throws IOException {
long start = atMin ? minValue : maxValue;
indexIterator = indexTable.longKeyIterator(minValue, maxValue, start);
if (indexIterator.hasNext()) {
indexIterator.next();
if (atMin) {
indexIterator.previous();
}
}
}
/**
* @param min
* @param max
* @param longValue
* @param before
*/
public IndexLongIterator(long minValue, long maxValue, long start, boolean before)
throws IOException {
indexIterator = indexTable.longKeyIterator(minValue, maxValue, start);
if (indexIterator.hasNext()) {
long val = indexIterator.next();
if (before || val != start) {
indexIterator.previous();
}
}
}
@Override
public boolean hasNext() throws IOException {
if (hasNext) {
return true;
}
try {
long key = indexIterator.next();
keyField = fieldType.newField();
keyField.setLongValue(key);
hasNext = true;
hasPrev = false;
}
catch (NoSuchElementException e) {
return false;
}
return true;
}
@Override
public boolean hasPrevious() throws IOException {
if (hasPrev) {
return true;
}
try {
long key = indexIterator.previous();
keyField = fieldType.newField();
keyField.setLongValue(key);
hasNext = false;
hasPrev = true;
}
catch (NoSuchElementException e) {
return false;
}
return true;
}
@Override
public Field next() throws IOException {
if (hasNext || hasNext()) {
hasNext = false;
hasPrev = true;
lastKey = keyField;
return keyField;
}
return null;
}
@Override
public Field previous() throws IOException {
if (hasPrev || hasPrevious()) {
hasNext = true;
hasPrev = false;
lastKey = keyField;
return keyField;
}
return null;
}
/**
* Delete all primary records which have the current
* index value (lastKey).
* @see db.DBFieldIterator#delete()
*/
@Override
public boolean delete() throws IOException {
if (lastKey == null) {
return false;
}
synchronized (db) {
IndexBuffer indexBuf = getIndexBuffer(lastKey);
if (indexBuf != null) {
long[] keys = indexBuf.getPrimaryKeys();
for (long key : keys) {
primaryTable.deleteRecord(key);
}
// The following does not actually delete the index record since it
// should already have been removed with the removal of all associated
// primary records. Invoking this method allows the iterator to
// recover from the index table change.
indexIterator.delete();
}
lastKey = null;
return true;
}
}
}
}
@@ -0,0 +1,199 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import java.io.IOException;
import db.buffers.DataBuffer;
import ghidra.util.exception.AssertException;
/**
* <code>FixedKeyFixedRecNode</code> is an implementation of a BTree leaf node
* which utilizes fixed-length key values and stores fixed-length records.
* <p>
* This type of node has the following layout within a single DataBuffer
* (field size in bytes, where 'L' is the fixed length of the fixed-length
* key as specified by key type in associated Schema):
* <pre>
* | NodeType(1) | KeyCount(4) | PrevLeafId(4) | NextLeafId(4) | Key0(L) | Rec0 | ...
*
* | KeyN(L) | RecN |
* </pre>
*/
class FixedKeyFixedRecNode extends FixedKeyRecordNode {
private static final int HEADER_SIZE = RECORD_LEAF_HEADER_SIZE;
private static final int ENTRY_BASE_OFFSET = HEADER_SIZE;
private static final int[] EMPTY_ID_LIST = new int[0];
private int entrySize;
private int recordLength;
/**
* Construct an existing fixed-length key fixed-length record leaf node.
* @param nodeMgr table node manager instance
* @param buf node buffer
* @throws IOException if IO error occurs
*/
FixedKeyFixedRecNode(NodeMgr nodeMgr, DataBuffer buf) throws IOException {
super(nodeMgr, buf);
this.recordLength = nodeMgr.getTableSchema().getFixedLength();
entrySize = keySize + recordLength;
}
/**
* Construct a new fixed-length key fixed-length record leaf node.
* @param nodeMgr table node manager instance
* @param prevLeafId node buffer id for previous leaf ( &lt; 0: no leaf)
* @param nextLeafId node buffer id for next leaf ( &lt; 0 : no leaf)
* @throws IOException if IO error occurs
*/
FixedKeyFixedRecNode(NodeMgr nodeMgr, int prevLeafId, int nextLeafId) throws IOException {
super(nodeMgr, NodeMgr.FIXEDKEY_FIXED_REC_NODE, prevLeafId, nextLeafId);
this.recordLength = nodeMgr.getTableSchema().getFixedLength();
entrySize = keySize + recordLength;
}
@Override
FixedKeyRecordNode createNewLeaf(int prevLeafId, int nextLeafId) throws IOException {
return new FixedKeyFixedRecNode(nodeMgr, prevLeafId, nextLeafId);
}
@Override
public int getKeyOffset(int index) {
return ENTRY_BASE_OFFSET + (index * entrySize);
}
/**
* Get the record offset within the buffer
* @param index key index
* @return record offset
*/
@Override
public int getRecordOffset(int index) {
return ENTRY_BASE_OFFSET + (index * entrySize);
}
/**
* Shift all records by one starting with index to the end.
* @param index the smaller key index (0 &lt;= index1)
* @param rightShift shift right by one record if true, else shift left by
* one record.
*/
private void shiftRecords(int index, boolean rightShift) {
// No movement needed for appended record
if (index == keyCount)
return;
// Determine block to be moved
int start = getRecordOffset(index);
int end = getRecordOffset(keyCount);
int len = end - start;
// Move record data
int offset = start + (rightShift ? entrySize : -entrySize);
buffer.move(start, offset, len);
}
@Override
public void remove(int index) {
if (index < 0 || index >= keyCount)
throw new AssertException();
shiftRecords(index + 1, false);
setKeyCount(keyCount - 1);
}
@Override
boolean insertRecord(int index, Record record) throws IOException {
// Check for use of indirect chained record node(s)
// int len = record.length();
if (keyCount == ((buffer.length() - HEADER_SIZE) / entrySize))
return false; // insufficient space for record storage
// Make room for new record
shiftRecords(index, true);
// Store new record
int offset = getRecordOffset(index);
record.getKeyField().write(buffer, offset);
record.write(buffer, offset + keySize);
setKeyCount(keyCount + 1);
return true;
}
@Override
FixedKeyNode updateRecord(int index, Record record) throws IOException {
int offset = getRecordOffset(index) + keySize;
record.write(buffer, offset);
return getRoot();
}
@Override
public Record getRecord(Field key, Schema schema) throws IOException {
int index = getKeyIndex(key);
if (index < 0)
return null;
Record record = schema.createRecord(key);
record.read(buffer, getRecordOffset(index) + keySize);
return record;
}
@Override
public Record getRecord(Schema schema, int index) throws IOException {
Field key = getKeyField(index);
Record record = schema.createRecord(key);
record.read(buffer, getRecordOffset(index) + keySize);
return record;
}
@Override
void splitData(FixedKeyRecordNode newRightLeaf) {
FixedKeyFixedRecNode rightNode = (FixedKeyFixedRecNode) newRightLeaf;
int splitIndex = keyCount / 2;
int count = keyCount - splitIndex;
int start = getRecordOffset(splitIndex); // start of block to be moved
int end = getRecordOffset(keyCount); // end of block to be moved
int splitLen = end - start; // length of block to be moved
// Copy data to new leaf node
rightNode.buffer.copy(ENTRY_BASE_OFFSET, buffer, start, splitLen);
// Adjust key counts
setKeyCount(keyCount - count);
rightNode.setKeyCount(count);
}
@Override
public void delete() throws IOException {
nodeMgr.deleteNode(this);
}
@Override
public int[] getBufferReferences() {
return EMPTY_ID_LIST;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,136 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import java.io.IOException;
import db.buffers.DataBuffer;
import ghidra.util.exception.AssertException;
/**
* <code>FixedKeyNode</code> is an abstract implementation of a BTree node
* which utilizes fixed-length key values.
* <pre>
* | NodeType(1) | KeyCount(4) | ...
* </pre>
*/
abstract class FixedKeyNode implements FieldKeyNode {
private static final int KEY_COUNT_SIZE = 4;
private static final int KEY_COUNT_OFFSET = NodeMgr.NODE_HEADER_SIZE;
static final int FIXEDKEY_NODE_HEADER_SIZE = NodeMgr.NODE_HEADER_SIZE + KEY_COUNT_SIZE;
protected final Field keyType;
protected final int keySize;
protected NodeMgr nodeMgr;
protected DataBuffer buffer;
protected FixedKeyInteriorNode parent;
protected int keyCount;
/**
* Construct an existing fixed-length key node.
* @param nodeMgr table node manager instance
* @param buf node buffer
* @throws IOException thrown if IO error occurs
*/
FixedKeyNode(NodeMgr nodeMgr, DataBuffer buf) throws IOException {
this.nodeMgr = nodeMgr;
buffer = buf;
Schema schema = nodeMgr.getTableSchema();
if (!schema.useFixedKeyNodes()) {
throw new AssertException("unsupported schema");
}
keyType = schema.getKeyFieldType();
keySize = keyType.length();
keyCount = buffer.getInt(KEY_COUNT_OFFSET);
nodeMgr.addNode(this);
}
/**
* Construct a new fixed-length key node.
* @param nodeMgr table node manager.
* @param nodeType node type
* @throws IOException thrown if IO error occurs
*/
FixedKeyNode(NodeMgr nodeMgr, byte nodeType) throws IOException {
this.nodeMgr = nodeMgr;
buffer = nodeMgr.getBufferMgr().createBuffer();
NodeMgr.setNodeType(buffer, nodeType);
Schema schema = nodeMgr.getTableSchema();
if (!schema.useFixedKeyNodes()) {
throw new AssertException("unsupported schema");
}
keyType = schema.getKeyFieldType();
keySize = keyType.length();
setKeyCount(0);
nodeMgr.addNode(this);
}
@Override
public FixedKeyInteriorNode getParent() {
return parent;
}
@Override
public int getBufferId() {
return buffer.getId();
}
@Override
public DataBuffer getBuffer() {
return buffer;
}
/**
* Get the root for this node. If setParent has not been invoked, this node
* is assumed to be the root.
* @return root node
*/
FixedKeyNode getRoot() {
if (parent != null) {
return parent.getRoot();
}
return this;
}
@Override
public int getKeyCount() {
return keyCount;
}
@Override
public void setKeyCount(int cnt) {
keyCount = cnt;
buffer.putInt(KEY_COUNT_OFFSET, keyCount);
}
/**
* Get the key value at a specific index.
* @param index key index
* @return key value
*/
abstract byte[] getKey(int index);
@Override
public final Field getKeyField(int index) {
Field key = keyType.newField();
key.setBinaryData(getKey(index));
return key;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,454 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import java.io.IOException;
import db.buffers.DataBuffer;
import ghidra.util.datastruct.IntArrayList;
import ghidra.util.exception.AssertException;
/**
* <code>FixedKeyVarRecNode</code> is an implementation of a BTree leaf node
* which utilizes fixed-length key values and stores variable-length records.
* <p>
* This type of node has the following layout within a single DataBuffer
* (field size in bytes, where 'L' is the fixed length of the fixed-length
* key as specified by key type in associated Schema)::
* <pre>
* | NodeType(1) | KeyCount(4) | PrevLeafId(4) | NextLeafId(4) | Key0(L) | RecOffset0(4) | IndFlag0(1) |...
*
* | KeyN(L) | RecOffsetN(4) | IndFlagN(1) |...&lt;FreeSpace&gt;... | RecN |... | Rec1 |
* </pre>
* IndFlag - if not zero the record has been stored within a chained DBBuffer
* whose 4-byte integer buffer ID has been stored within this leaf at the record offset.
*/
class FixedKeyVarRecNode extends FixedKeyRecordNode {
private static final int HEADER_SIZE = RECORD_LEAF_HEADER_SIZE;
private static final int OFFSET_SIZE = 4;
private static final int INDIRECT_OPTION_SIZE = 1;
private static final int KEY_BASE_OFFSET = HEADER_SIZE;
private final int entrySize;
private final int dataOffsetBaseOffset;
private final int indirectOptionBaseOffset;
/**
* Construct an existing fixed-length key variable-length record leaf node.
* @param nodeMgr table node manager instance
* @param buf node buffer
* @throws IOException if IO error occurs
*/
FixedKeyVarRecNode(NodeMgr nodeMgr, DataBuffer buf) throws IOException {
super(nodeMgr, buf);
entrySize = keySize + OFFSET_SIZE + INDIRECT_OPTION_SIZE;
dataOffsetBaseOffset = KEY_BASE_OFFSET + keySize;
indirectOptionBaseOffset = dataOffsetBaseOffset + OFFSET_SIZE;
}
/**
* Construct a new fixed-length key variable-length record leaf node.
* @param nodeMgr table node manager instance
* @param prevLeafId node buffer id for previous leaf ( &lt; 0: no leaf)
* @param nextLeafId node buffer id for next leaf ( &lt; 0 : no leaf)
* @throws IOException if IO error occurs
*/
FixedKeyVarRecNode(NodeMgr nodeMgr, int prevLeafId, int nextLeafId) throws IOException {
super(nodeMgr, NodeMgr.FIXEDKEY_VAR_REC_NODE, prevLeafId, nextLeafId);
entrySize = keySize + OFFSET_SIZE + INDIRECT_OPTION_SIZE;
dataOffsetBaseOffset = KEY_BASE_OFFSET + keySize;
indirectOptionBaseOffset = dataOffsetBaseOffset + OFFSET_SIZE;
}
@Override
FixedKeyRecordNode createNewLeaf(int prevLeafId, int nextLeafId) throws IOException {
return new FixedKeyVarRecNode(nodeMgr, prevLeafId, nextLeafId);
}
@Override
public int getKeyOffset(int index) {
return KEY_BASE_OFFSET + (index * entrySize);
}
/**
* Get the record offset within the buffer
* @param index key index
* @return record offset
*/
public int getRecordDataOffset(int index) {
return buffer.getInt(dataOffsetBaseOffset + (index * entrySize));
}
/**
* Store the record offset within the buffer for the specified key index
* @param index key index
* @param offset record offset
*/
private void putRecordDataOffset(int index, int offset) {
buffer.putInt(dataOffsetBaseOffset + (index * entrySize), offset);
}
/**
* Determine if a record is utilizing a chained DBBuffer for data storage
* @param index key index
* @return true if indirect storage is used for record, else false
*/
private boolean hasIndirectStorage(int index) {
return buffer.getByte(indirectOptionBaseOffset + (index * entrySize)) != 0;
}
/**
* Set the indirect storage flag associated with a record
* @param index key index
* @param state indirect storage used (true) or not used (false)
*/
private void enableIndirectStorage(int index, boolean state) {
buffer.putByte(indirectOptionBaseOffset + (index * entrySize), state ? (byte) 1 : (byte) 0);
}
/**
* @return unused free space within node
*/
private int getFreeSpace() {
return (keyCount == 0 ? buffer.length() : getRecordDataOffset(keyCount - 1)) -
(keyCount * entrySize) - RECORD_LEAF_HEADER_SIZE;
}
/**
* Get the length of a stored record.
* @param index index associated with record.
*/
private int getRecordLength(int index) {
if (index == 0) {
return buffer.length() - getRecordDataOffset(0);
}
return getRecordDataOffset(index - 1) - getRecordDataOffset(index);
}
/**
* Get the length of a stored record. Optimized if record offset
* already known.
* @param index index associated with record.
* @param offset record offset
*/
private int getRecordLength(int index, int offset) {
if (index == 0) {
return buffer.length() - offset;
}
return getRecordDataOffset(index - 1) - offset;
}
/**
* Move all record data, starting with index, by the specified offset amount.
* If the node contains 5 records, an index of 3 would shift the record data
* for indexes 3 and 4 left by the spacified offset amount. This is used to
* make space for a new or updated record.
* @param index the smaller key/record index (0 &lt;= index1)
* @param offset movement offset in bytes
* @return insertion offset immediately following moved block.
*/
private int moveRecords(int index, int offset) {
int lastIndex = keyCount - 1;
// No movement needed for appended record
if (index == keyCount) {
if (index == 0) {
return buffer.length() + offset;
}
return getRecordDataOffset(lastIndex) + offset;
}
// Determine block to be moved
int start = getRecordDataOffset(lastIndex);
int end = (index == 0) ? buffer.length() : getRecordDataOffset(index - 1);
int len = end - start;
// Move record data
buffer.move(start, start + offset, len);
// Adjust stored offsets
for (int i = index; i < keyCount; i++) {
putRecordDataOffset(i, getRecordDataOffset(i) + offset);
}
return end + offset;
}
@Override
public Record getRecord(Schema schema, int index) throws IOException {
Field key = getKeyField(index);
Record record = schema.createRecord(key);
if (hasIndirectStorage(index)) {
int bufId = buffer.getInt(getRecordDataOffset(index));
ChainedBuffer chainedBuffer = new ChainedBuffer(nodeMgr.getBufferMgr(), bufId);
record.read(chainedBuffer, 0);
}
else {
record.read(buffer, getRecordDataOffset(index));
}
return record;
}
@Override
public int getRecordOffset(int index) throws IOException {
if (hasIndirectStorage(index)) {
return -buffer.getInt(getRecordDataOffset(index));
}
return getRecordDataOffset(index);
}
@Override
public Record getRecord(Field key, Schema schema) throws IOException {
int index = getKeyIndex(key);
if (index < 0)
return null;
return getRecord(schema, index);
}
/**
* Find the index which represents the halfway point within the record data.
* @returns key index.
*/
private int getSplitIndex() {
int halfway = ((keyCount == 0 ? buffer.length() : getRecordDataOffset(keyCount - 1)) +
buffer.length()) / 2;
int min = 1;
int max = keyCount - 1;
while (min < max) {
int i = (min + max) / 2;
int offset = getRecordDataOffset(i);
if (offset == halfway) {
return i;
}
else if (offset < halfway) {
max = i - 1;
}
else {
min = i + 1;
}
}
return min;
}
@Override
void splitData(FixedKeyRecordNode newRightLeaf) {
FixedKeyVarRecNode rightNode = (FixedKeyVarRecNode) newRightLeaf;
int splitIndex = getSplitIndex();
int count = keyCount - splitIndex;
int start = getRecordDataOffset(keyCount - 1); // start of block to be moved
int end = getRecordDataOffset(splitIndex - 1); // end of block to be moved
int splitLen = end - start; // length of block to be moved
int rightOffset = buffer.length() - splitLen; // data offset within new leaf node
// Copy data to new leaf node
DataBuffer newBuf = rightNode.buffer;
newBuf.copy(rightOffset, buffer, start, splitLen);
newBuf.copy(KEY_BASE_OFFSET, buffer, KEY_BASE_OFFSET + (splitIndex * entrySize),
count * entrySize);
// Fix record offsets in new leaf node
int offsetCorrection = buffer.length() - end;
for (int i = 0; i < count; i++) {
rightNode.putRecordDataOffset(i, rightNode.getRecordDataOffset(i) + offsetCorrection);
}
// Adjust key counts
setKeyCount(keyCount - count);
rightNode.setKeyCount(count);
}
@Override
FixedKeyNode updateRecord(int index, Record record) throws IOException {
int offset = getRecordDataOffset(index);
int oldLen = getRecordLength(index, offset);
int len = record.length();
// Check for use of indirect chained record node(s)
int maxRecordLength = ((buffer.length() - HEADER_SIZE) >> 2) - entrySize; // min 4 records per node
boolean wasIndirect = hasIndirectStorage(index);
boolean useIndirect = (len > maxRecordLength);
if (useIndirect) {
// Store record in chained buffers
len = 4;
ChainedBuffer chainedBuffer = null;
if (wasIndirect) {
chainedBuffer = new ChainedBuffer(nodeMgr.getBufferMgr(), buffer.getInt(offset));
chainedBuffer.setSize(record.length(), false);
}
else {
chainedBuffer = new ChainedBuffer(record.length(), nodeMgr.getBufferMgr());
buffer.putInt(offset + oldLen - 4, chainedBuffer.getId()); // assumes old len is always > 4
enableIndirectStorage(index, true);
}
record.write(chainedBuffer, 0);
}
else if (wasIndirect) {
removeChainedBuffer(buffer.getInt(offset));
enableIndirectStorage(index, false);
}
// See if updated record will fit in current buffer
if (useIndirect || len <= (getFreeSpace() + oldLen)) {
// Overwrite record data - move other data if needed
int dataShift = oldLen - len;
if (dataShift != 0) {
offset = moveRecords(index + 1, dataShift);
putRecordDataOffset(index, offset);
}
if (!useIndirect) {
record.write(buffer, offset);
}
return getRoot();
}
// Insufficient room for updated record - remove and re-add
Field key = record.getKeyField();
FixedKeyRecordNode leaf = (FixedKeyRecordNode) deleteRecord(key, null).getLeafNode(key);
return leaf.putRecord(record, null);
}
/**
* Insert the specified record at the specified key index.
* Existing data may be shifted within the buffer to make room for
* the new record. Parent must be notified if this changes the leftmost
* key.
* @param index insertion index for stored key
* @param record record to be inserted
* @throws IOException thrown if an IO error occurs
*/
@Override
boolean insertRecord(int index, Record record) throws IOException {
// Check for use of indirect chained record node(s)
int len = record.length();
int maxRecordLength = ((buffer.length() - HEADER_SIZE) >> 2) - entrySize; // min 4 records per node
boolean useIndirect = (len > maxRecordLength);
if (useIndirect) {
len = 4;
}
if ((len + entrySize) > getFreeSpace())
return false; // insufficient space for record storage
// Make room for new record
int offset = moveRecords(index, -len);
// Make room for new key/offset entry
int start = KEY_BASE_OFFSET + (index * entrySize);
len = (keyCount - index) * entrySize;
buffer.move(start, start + entrySize, len);
// Store new record key/offset
buffer.put(start, record.getKeyField().getBinaryData());
buffer.putInt(start + keySize, offset);
setKeyCount(keyCount + 1);
// Store record data
if (useIndirect) {
ChainedBuffer chainedBuffer =
new ChainedBuffer(record.length(), nodeMgr.getBufferMgr());
buffer.putInt(offset, chainedBuffer.getId());
record.write(chainedBuffer, 0);
}
else {
record.write(buffer, offset);
}
enableIndirectStorage(index, useIndirect);
return true;
}
@Override
public void remove(int index) throws IOException {
if (index < 0 || index >= keyCount)
throw new AssertException();
if (hasIndirectStorage(index)) {
removeChainedBuffer(buffer.getInt(getRecordDataOffset(index)));
enableIndirectStorage(index, false);
}
int len = getRecordLength(index);
moveRecords(index + 1, len);
int start = KEY_BASE_OFFSET + ((index + 1) * entrySize);
len = (keyCount - index - 1) * entrySize;
buffer.move(start, start - entrySize, len);
setKeyCount(keyCount - 1);
}
@Override
public FixedKeyNode removeLeaf() throws IOException {
// Remove all chained buffers associated with this leaf
for (int index = 0; index < keyCount; ++index) {
if (hasIndirectStorage(index)) {
removeChainedBuffer(buffer.getInt(getRecordDataOffset(index)));
}
}
return super.removeLeaf();
}
/**
* Remove a chained buffer.
* @param bufferId chained buffer ID
*/
private void removeChainedBuffer(int bufferId) throws IOException {
ChainedBuffer chainedBuffer = new ChainedBuffer(nodeMgr.getBufferMgr(), bufferId);
chainedBuffer.delete();
}
@Override
public void delete() throws IOException {
// Remove all chained buffers associated with this node.
for (int index = 0; index < keyCount; index++) {
if (hasIndirectStorage(index)) {
int offset = getRecordDataOffset(index);
int bufferId = buffer.getInt(offset);
removeChainedBuffer(bufferId);
buffer.putInt(offset, -1);
}
}
// Remove this node
nodeMgr.deleteNode(this);
}
@Override
public int[] getBufferReferences() {
IntArrayList idList = new IntArrayList();
for (int i = 0; i < keyCount; i++) {
if (hasIndirectStorage(i)) {
int offset = getRecordDataOffset(i);
idList.add(buffer.getInt(offset));
}
}
return idList.toArray();
}
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +15,10 @@
*/
package db;
import ghidra.util.exception.AssertException;
import java.io.IOException;
import db.buffers.DataBuffer;
import ghidra.util.exception.AssertException;
/**
* <code>FixedRecNode</code> is an implementation of a BTree leaf node
@@ -37,75 +35,68 @@ import db.buffers.DataBuffer;
class FixedRecNode extends LongKeyRecordNode {
private static final int HEADER_SIZE = RECORD_LEAF_HEADER_SIZE;
private static final int ENTRY_BASE_OFFSET = HEADER_SIZE;
private static final int KEY_SIZE = 8;
private static final int[] EMPTY_ID_LIST = new int[0];
private int entrySize;
private int recordLength;
/**
* Construct an existing long-key fixed-length record leaf node.
* @param nodeMgr table node manager instance
* @param buf node buffer
* @param recordLength fixed record length
*/
FixedRecNode(NodeMgr nodeMgr, DataBuffer buf, int recordLength) {
super(nodeMgr, buf);
this.recordLength = recordLength;
entrySize = KEY_SIZE + recordLength;
}
/**
* Construct a new long-key fixed-length record leaf node.
* @param nodeMgr table node manager instance
* @param recordLength fixed record length
* @param prevLeafId node buffer id for previous leaf ( &lt; 0: no leaf)
* @param nextLeafId node buffer id for next leaf ( &lt; 0 : no leaf)
* @throws IOException
* @throws IOException thrown if IO error occurs
*/
FixedRecNode(NodeMgr nodeMgr, int recordLength, int prevLeafId, int nextLeafId) throws IOException {
FixedRecNode(NodeMgr nodeMgr, int recordLength, int prevLeafId, int nextLeafId)
throws IOException {
super(nodeMgr, NodeMgr.LONGKEY_FIXED_REC_NODE, prevLeafId, nextLeafId);
this.recordLength = recordLength;
entrySize = KEY_SIZE + recordLength;
}
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#createNewLeaf(int, int)
*/
@Override
LongKeyRecordNode createNewLeaf(int prevLeafId, int nextLeafId) throws IOException {
LongKeyRecordNode createNewLeaf(int prevLeafId, int nextLeafId) throws IOException {
return new FixedRecNode(nodeMgr, recordLength, prevLeafId, nextLeafId);
}
/*
* @see ghidra.framework.store.db.LongKeyNode#getKey(int)
*/
@Override
long getKey(int index) {
return buffer.getLong(ENTRY_BASE_OFFSET + (index * entrySize));
long getKey(int index) {
return buffer.getLong(getKeyOffset(index));
}
// /**
// * Store a key at the specified index
// * @param index key index
// * @param key key value
// */
// private void putKey(int index, long key) {
// buffer.putLong(ENTRY_BASE_OFFSET + (index * entrySize), key);
// }
@Override
public int getKeyOffset(int index) {
return ENTRY_BASE_OFFSET + (index * entrySize);
}
/**
* Get the record offset within the buffer
* @param index key index
* @return record offset
*/
private int getRecordOffset(int index) {
@Override
public int getRecordOffset(int index) {
return ENTRY_BASE_OFFSET + (index * entrySize);
}
/**
* Shift all records by one starting with index to the end.
* @param index the smaller key index (0 &lt;= index1)
@@ -113,49 +104,41 @@ class FixedRecNode extends LongKeyRecordNode {
* one record.
*/
private void shiftRecords(int index, boolean rightShift) {
// No movement needed for appended record
if (index == keyCount)
return;
// Determine block to be moved
int start = getRecordOffset(index);
int end = getRecordOffset(keyCount);
int len = end - start;
// Move record data
int offset = start + (rightShift ? entrySize : -entrySize);
buffer.move(start, offset, len);
}
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#remove(int)
*/
@Override
void remove(int index) {
if (index < 0 || index >= keyCount)
throw new AssertException();
@Override
public void remove(int index) {
if (index < 0 || index >= keyCount)
throw new AssertException();
shiftRecords(index + 1, false);
setKeyCount(keyCount - 1);
setKeyCount(keyCount - 1);
}
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#insertRecord(int, ghidra.framework.store.db.Record)
*/
@Override
boolean insertRecord(int index, Record record) throws IOException {
// Check for use of indirect chained record node(s)
// int len = record.length();
boolean insertRecord(int index, Record record) throws IOException {
if (keyCount == ((buffer.length() - HEADER_SIZE) / entrySize))
if (keyCount == ((buffer.length() - HEADER_SIZE) / entrySize)) {
return false; // insufficient space for record storage
}
// Make room for new record
shiftRecords(index, true);
// Store new record
int offset = getRecordOffset(index);
buffer.putLong(offset, record.getKey());
@@ -165,21 +148,15 @@ throw new AssertException();
return true;
}
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#updateRecord(int, ghidra.framework.store.db.Record)
*/
@Override
LongKeyNode updateRecord(int index, Record record) throws IOException {
LongKeyNode updateRecord(int index, Record record) throws IOException {
int offset = getRecordOffset(index) + KEY_SIZE;
record.write(buffer, offset);
return getRoot();
}
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#getRecord(long, ghidra.framework.store.db.Schema)
*/
@Override
Record getRecord(long key, Schema schema) throws IOException {
Record getRecord(long key, Schema schema) throws IOException {
int index = getKeyIndex(key);
if (index < 0)
return null;
@@ -188,50 +165,39 @@ throw new AssertException();
return record;
}
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#getRecord(ghidra.framework.store.db.Schema, int)
*/
@Override
Record getRecord(Schema schema, int index) throws IOException {
public Record getRecord(Schema schema, int index) throws IOException {
long key = getKey(index);
Record record = schema.createRecord(key);
record.read(buffer, getRecordOffset(index) + KEY_SIZE);
return record;
return record;
}
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#splitData(ghidra.framework.store.db.LongKeyRecordNode)
*/
@Override
void splitData(LongKeyRecordNode newRightLeaf) {
void splitData(LongKeyRecordNode newRightLeaf) {
FixedRecNode rightNode = (FixedRecNode) newRightLeaf;
int splitIndex = keyCount / 2;
int count = keyCount - splitIndex;
int start = getRecordOffset(splitIndex); // start of block to be moved
int end = getRecordOffset(keyCount); // end of block to be moved
int splitLen = end - start; // length of block to be moved
// Copy data to new leaf node
rightNode.buffer.copy(ENTRY_BASE_OFFSET, buffer, start, splitLen);
rightNode.buffer.copy(ENTRY_BASE_OFFSET, buffer, start, splitLen);
// Adjust key counts
setKeyCount(keyCount - count);
rightNode.setKeyCount(count);
}
/*
* @see ghidra.framework.store.db.LongKeyNode#delete()
*/
@Override
public void delete() throws IOException {
public void delete() throws IOException {
nodeMgr.deleteNode(this);
}
/*
* @see ghidra.framework.store.db.BTreeNode#getBufferReferences()
*/
@Override
public int[] getBufferReferences() {
return EMPTY_ID_LIST;
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,4 +26,13 @@ public class IllegalFieldAccessException extends RuntimeException {
IllegalFieldAccessException() {
super("Illegal field access");
}
/**
* Construct an illegal field access exception
* with a specific message
*/
IllegalFieldAccessException(String msg) {
super(msg);
}
}
@@ -1,237 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db;
import java.io.IOException;
import db.buffers.DataBuffer;
/**
* <code>IndexBuffer</code> stores index data for a common index key
* within a data buffer. The index data has the following layout (field size in
* bytes):
* <pre>
* | FieldType(1) | KeyCount(4) | PrimeKey1(8) | ... | PrimeKeyN(8) |
* </pre>
* This type of index buffer is used to store primary keys associated with a
* single secondary key. The association to a specific secondary key
* is handled by the <code>IndexTable</code>. The primary keys are maintained
* within the buffer in an asscending sorted order.
*/
class IndexBuffer {
private static final int FIELD_TYPE_SIZE = 1;
private static final int KEY_COUNT_SIZE = 4;
private static final int FIELD_TYPE_OFFSET = 0;
private static final int KEY_COUNT_OFFSET = FIELD_TYPE_OFFSET + FIELD_TYPE_SIZE;
static final int INDEX_HEADER_SIZE = FIELD_TYPE_SIZE + KEY_COUNT_SIZE;
static final int PRIMARY_KEY_SIZE = 8;
Field indexKey;
int keyCount;
IndexDataBuffer dataBuffer;
/**
* Construct a new index buffer.
* @param indexKey associated index key
* @param data existing index buffer data from storage or null for an
* empty index buffer.
* @throws IOException thrown if IO error occurs
*/
IndexBuffer(Field indexKey, byte[] data) throws IOException {
this.indexKey = indexKey;
if (data == null) {
dataBuffer = new IndexDataBuffer(INDEX_HEADER_SIZE);
dataBuffer.putByte(FIELD_TYPE_OFFSET, indexKey.getFieldType());
dataBuffer.putInt(KEY_COUNT_OFFSET, 0);
}
else {
if (data[FIELD_TYPE_OFFSET] != indexKey.getFieldType())
throw new IOException("Invalid index data");
dataBuffer = new IndexDataBuffer(data);
}
keyCount = dataBuffer.getInt(KEY_COUNT_OFFSET);
}
/**
* Get the associated index key
* @return index key
*/
Field getIndexKey() {
return indexKey;
}
/**
* Set the stored primary key count
* @param cnt primary key count
*/
private void setKeyCount(int cnt) {
keyCount = cnt;
dataBuffer.putInt(KEY_COUNT_OFFSET, keyCount);
}
/**
* Provides data buffer manipulation for the index data
*/
class IndexDataBuffer extends DataBuffer {
/**
* Construct an index data buffer.
* @see db.buffers.DataBuffer#DataBuffer(byte[])
*/
IndexDataBuffer(byte[] data) {
super(data);
}
/**
* Construct an index data buffer.
* @see db.buffers.DataBuffer#DataBuffer(int)
*/
IndexDataBuffer(int size) {
super(size);
}
/**
* Get the storage array associated with this buffer.
* @return byte storage array.
*/
@Override
protected byte[] getData() {
return data;
}
/**
* Get the storage array associated with this buffer.
* @return byte storage array.
*/
@Override
protected void setData(byte[] data) {
this.data = data;
}
}
/**
* Get the index buffer data.
* @return index data or null if index data is empty.
*/
byte[] getData() {
byte[] data = dataBuffer.getData();
if (data.length <= INDEX_HEADER_SIZE)
return null;
return data;
}
/**
* Get the primary key associated with the specified entry index.
* This method does not perform any bounds checking on the index value.
* @param index index entry index.
* @return primary key associated with entry.
*/
long getPrimaryKey(int index) {
return dataBuffer.getLong(INDEX_HEADER_SIZE + (index * PRIMARY_KEY_SIZE));
}
/**
* Get the secondary key index within the buffer.
* @param primaryKey primary key
* @return key index if found, else -(key index + 1) indicates insertion
* point.
*/
int getIndex(long primaryKey) {
return getKeyIndex(primaryKey);
}
/**
* Perform a binary search to locate the specified primary key.
* @param primaryKey primary key
* @return key index if found, else -(key index + 1) indicates insertion
* point.
*/
private int getKeyIndex(long primaryKey) {
int min = 0;
int max = keyCount - 1;
while (min <= max) {
int i = (min + max)/2;
long k = getPrimaryKey(i);
if (k == primaryKey) {
return i;
}
else if (k < primaryKey) {
min = i + 1;
}
else {
max = i - 1;
}
}
return -(min+1);
}
/**
* Add a new primary key to this index buffer.
* @param primaryKey primary key
*/
void addEntry(long primaryKey) {
int index = getKeyIndex(primaryKey);
if (index < 0) {
index = -index-1;
IndexDataBuffer newDataBuffer = new IndexDataBuffer(dataBuffer.length() + PRIMARY_KEY_SIZE);
int len = INDEX_HEADER_SIZE + (index * PRIMARY_KEY_SIZE);
newDataBuffer.copy(0, dataBuffer, 0, len);
newDataBuffer.copy(len + PRIMARY_KEY_SIZE, dataBuffer, len, dataBuffer.length() - len);
newDataBuffer.putLong(len, primaryKey);
dataBuffer = newDataBuffer;
setKeyCount(keyCount + 1);
}
}
/**
* Delete the specified index entry from this index buffer.
* @param primaryKey primary key
*/
void deleteEntry(long primaryKey) {
int index = getKeyIndex(primaryKey);
if (index >= 0) {
IndexDataBuffer newDataBuffer = new IndexDataBuffer(dataBuffer.length() - PRIMARY_KEY_SIZE);
int len = INDEX_HEADER_SIZE + (index * PRIMARY_KEY_SIZE);
newDataBuffer.copy(0, dataBuffer, 0, len);
newDataBuffer.copy(len, dataBuffer, len + PRIMARY_KEY_SIZE, dataBuffer.length() - len - PRIMARY_KEY_SIZE);
dataBuffer = newDataBuffer;
setKeyCount(keyCount - 1);
}
}
/**
* Get the list of primary keys contained within this index buffer.
* @return long[] list of primary keys
* @throws IOException thrown if IO error occurs
*/
long[] getPrimaryKeys() {
long[] keys = new long[keyCount];
for (int i = 0; i < keyCount; i++) {
keys[i] = getPrimaryKey(i);
}
return keys;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,15 +15,30 @@
*/
package db;
import ghidra.util.exception.AssertException;
import java.io.IOException;
import db.buffers.DataBuffer;
/**
* <code>IntField</code> provides a wrapper for 4-byte signed integer data
* which is read or written to a Record.
*/
public class IntField extends Field {
public final class IntField extends Field {
/**
* Minimum integer field value
*/
public static final IntField MIN_VALUE = new IntField(Integer.MIN_VALUE, true);
/**
* Maximum integer field value
*/
public static final IntField MAX_VALUE = new IntField(Integer.MAX_VALUE, true);
/**
* Instance intended for defining a {@link Table} {@link Schema}
*/
public static final IntField INSTANCE = MIN_VALUE;
private int value;
@@ -39,69 +53,57 @@ public class IntField extends Field {
* @param i initial value
*/
public IntField(int i) {
value = i;
this(i, false);
}
/**
* @see db.Field#getIntValue()
* Construct an integer field with an initial value of i.
* @param i initial value
* @param immutable true if field value is immutable
*/
IntField(int i, boolean immutable) {
super(immutable);
value = i;
}
@Override
public int getIntValue() {
return value;
}
/**
* @see db.Field#setIntValue(int)
*/
@Override
public void setIntValue(int value) {
checkImmutable();
this.value = value;
}
/**
* @see db.Field#length()
*/
@Override
int length() {
return 4;
}
/**
* @see db.Field#write(ghidra.framework.store.Buffer, int)
*/
@Override
int write(Buffer buf, int offset) throws IOException {
return buf.putInt(offset, value);
}
/**
* @see db.Field#read(ghidra.framework.store.Buffer, int)
*/
@Override
int read(Buffer buf, int offset) throws IOException {
checkImmutable();
value = buf.getInt(offset);
return offset + 4;
}
/**
* @see db.Field#readLength(ghidra.framework.store.Buffer, int)
*/
@Override
int readLength(Buffer buf, int offset) throws IOException {
return 4;
}
/**
* @see db.Field#getFieldType()
*/
@Override
protected byte getFieldType() {
byte getFieldType() {
return INT_TYPE;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "IntField: " + Integer.toString(value);
@@ -109,12 +111,9 @@ public class IntField extends Field {
@Override
public String getValueAsString() {
return Integer.toHexString(value);
return "0x" + Integer.toHexString(value);
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof IntField))
@@ -122,9 +121,6 @@ public class IntField extends Field {
return ((IntField) obj).value == value;
}
/**
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Field o) {
IntField f = (IntField) o;
@@ -135,54 +131,64 @@ public class IntField extends Field {
return 1;
}
/**
* @see db.Field#newField(docking.widgets.fieldpanel.Field)
*/
@Override
public Field newField(Field fieldValue) {
if (fieldValue.isVariableLength())
throw new AssertException();
return new IntField((int) fieldValue.getLongValue());
int compareTo(DataBuffer buffer, int offset) {
int otherValue = buffer.getInt(offset);
if (value == otherValue)
return 0;
else if (value < otherValue)
return -1;
return 1;
}
/**
* @see db.Field#newField()
*/
@Override
public Field newField() {
public IntField copyField() {
return new IntField((int) getLongValue());
}
@Override
public IntField newField() {
return new IntField();
}
/**
* @see db.Field#getLongValue()
*/
@Override
public long getLongValue() {
return value;
}
/**
* @see db.Field#setLongValue(long)
*/
@Override
public void setLongValue(long value) {
this.value = (int) value;
setIntValue((int) value);
}
/**
* @see db.Field#getBinaryData()
*/
@Override
public byte[] getBinaryData() {
return new byte[] { (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8),
(byte) value };
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public void setBinaryData(byte[] bytes) {
checkImmutable();
if (bytes.length != 4) {
throw new IllegalFieldAccessException();
}
value = ((bytes[0] & 0xff) << 24) | ((bytes[1] & 0xff) << 16) | ((bytes[2] & 0xff) << 8) |
(bytes[3] & 0xff);
}
@Override
public int hashCode() {
return value;
}
@Override
IntField getMinValue() {
return MIN_VALUE;
}
@Override
IntField getMaxValue() {
return MAX_VALUE;
}
}
@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.feature.vt.api.db;
public class VTAppliedMarkupTableDBAdapterV0 {
package db;
/**
* Marker interface for {@link Table} interior nodes within the BTree structure.
*/
public interface InteriorNode extends BTreeNode {
// marker interface only
}

Some files were not shown because too many files have changed in this diff Show More