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 * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,12 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
// Performs database consistency check on the current program // Performs database consistency check on the current program
import db.DBHandle;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
import ghidra.program.model.listing.Program;
import db.DBHandle;
public class ConsistencyCheck extends GhidraScript { public class ConsistencyCheck extends GhidraScript {
@@ -56,6 +54,8 @@ public class ConsistencyCheck extends GhidraScript {
return; return;
} }
monitor.checkCanceled();
if (!df.canSave() || !currentProgram.hasExclusiveAccess()) { if (!df.canSave() || !currentProgram.hasExclusiveAccess()) {
popup("Program database is NOT consistent!\nRebuild requires exclusive checkout."); popup("Program database is NOT consistent!\nRebuild requires exclusive checkout.");
return; return;
@@ -67,19 +67,22 @@ public class ConsistencyCheck extends GhidraScript {
} }
end(false); end(false);
ProgramDB program = (ProgramDB) df.getDomainObject(this, false, false, monitor);
programMgr.closeProgram(currentProgram, true); programMgr.closeProgram(currentProgram, true);
currentProgram = (Program) df.getDomainObject(this, false, false, monitor); monitor.clearCanceled(); // compensate for Script Manager cancelling task on program close
dbh = ((ProgramDB) currentProgram).getDBHandle();
dbh = program.getDBHandle();
try { try {
boolean success = false; boolean success = false;
long txId = dbh.startTransaction(); int txId = program.startTransaction("Rebuild DB Indexes");
try { try {
success = dbh.rebuild(monitor); success = dbh.rebuild(monitor);
} }
finally { finally {
dbh.endTransaction(txId, success); program.endTransaction(txId, success);
} }
if (!success) { if (!success) {
@@ -92,11 +95,12 @@ public class ConsistencyCheck extends GhidraScript {
return; return;
} }
currentProgram.save("DB Rebuild", monitor); program.save("DB Rebuild", monitor);
} }
finally { finally {
currentProgram.release(this); programMgr.openProgram(program);
currentProgram = programMgr.openProgram(df); program.release(this);
currentProgram = program;
start(); start();
} }
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,9 +15,6 @@
*/ */
package ghidra.app.plugin.debug.dbtable; package ghidra.app.plugin.debug.dbtable;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@@ -26,6 +22,8 @@ import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
import db.*; import db.*;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
public class DbLargeTableModel implements TableModel { public class DbLargeTableModel implements TableModel {
private ArrayList<TableModelListener> listeners = new ArrayList<TableModelListener>(); private ArrayList<TableModelListener> listeners = new ArrayList<TableModelListener>();
@@ -43,7 +41,7 @@ public class DbLargeTableModel implements TableModel {
this.table = table; this.table = table;
schema = table.getSchema(); schema = table.getSchema();
try { try {
keyType = schema.getKeyFieldClass().newInstance(); keyType = schema.getKeyFieldType();
} }
catch (Exception e) { catch (Exception e) {
Msg.error(this, "Unexpected Exception: " + e.getMessage(), 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); Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
} }
columns.add(getColumn(schema.getKeyFieldClass())); columns.add(getColumn(schema.getKeyFieldType()));
Class<?>[] classes = schema.getFieldClasses(); Field[] fields = schema.getFields();
int fieldCount = schema.getFieldCount(); for (Field field : fields) {
for (int i = 0; i < fieldCount; i++) { columns.add(getColumn(field));
columns.add(getColumn(classes[i]));
} }
} }
private AbstractColumnAdapter getColumn(Class<?> c) { private AbstractColumnAdapter getColumn(Field field) {
if (c == ByteField.class) { if (field instanceof ByteField) {
return new ByteColumnAdapter(); return new ByteColumnAdapter();
} }
else if (c == BooleanField.class) { else if (field instanceof BooleanField) {
return new BooleanColumnAdapter(); return new BooleanColumnAdapter();
} }
else if (c == ShortField.class) { else if (field instanceof ShortField) {
return new ShortColumnAdapter(); return new ShortColumnAdapter();
} }
else if (c == IntField.class) { else if (field instanceof IntField) {
return new IntegerColumnAdapter(); return new IntegerColumnAdapter();
} }
else if (c == LongField.class) { else if (field instanceof LongField) {
return new LongColumnAdapter(); return new LongColumnAdapter();
} }
else if (c == StringField.class) { else if (field instanceof StringField) {
return new StringColumnAdapter(); return new StringColumnAdapter();
} }
else if (c == BinaryField.class) { else if (field instanceof BinaryField) {
return new BinaryColumnAdapter(); 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 { private void findMinKey() throws IOException {
@@ -36,12 +36,11 @@ public class DbSmallTableModel extends AbstractSortedTableModel<Record> {
records = new ArrayList<>(table.getRecordCount()); records = new ArrayList<>(table.getRecordCount());
columns.add(getColumn(schema.getKeyFieldClass())); columns.add(getColumn(schema.getKeyFieldType()));
Class<?>[] classes = schema.getFieldClasses(); Field[] fields = schema.getFields();
int fieldCount = schema.getFieldCount(); for (Field field : fields) {
for (int i = 0; i < fieldCount; i++) { columns.add(getColumn(field));
columns.add(getColumn(classes[i]));
} }
try { try {
@@ -55,29 +54,30 @@ public class DbSmallTableModel extends AbstractSortedTableModel<Record> {
} }
} }
private AbstractColumnAdapter getColumn(Class<?> c) { private AbstractColumnAdapter getColumn(Field field) {
if (c == ByteField.class) { if (field instanceof ByteField) {
return new ByteColumnAdapter(); return new ByteColumnAdapter();
} }
else if (c == BooleanField.class) { else if (field instanceof BooleanField) {
return new BooleanColumnAdapter(); return new BooleanColumnAdapter();
} }
else if (c == ShortField.class) { else if (field instanceof ShortField) {
return new ShortColumnAdapter(); return new ShortColumnAdapter();
} }
else if (c == IntField.class) { else if (field instanceof IntField) {
return new IntegerColumnAdapter(); return new IntegerColumnAdapter();
} }
else if (c == LongField.class) { else if (field instanceof LongField) {
return new LongColumnAdapter(); return new LongColumnAdapter();
} }
else if (c == StringField.class) { else if (field instanceof StringField) {
return new StringColumnAdapter(); return new StringColumnAdapter();
} }
else if (c == BinaryField.class) { else if (field instanceof BinaryField) {
return new BinaryColumnAdapter(); 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 @Override
@@ -66,7 +66,7 @@ public class AddressIndexPrimaryKeyIteratorTest extends AbstractGhidraHeadedInte
// Create table with indexed address column // Create table with indexed address column
Schema schema = 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(); DBHandle handle = program.getDBHandle();
myTable = handle.createTable("MyTable", schema, new int[] { 0 }); myTable = handle.createTable("MyTable", schema, new int[] { 0 });
@@ -32,7 +32,7 @@ import ghidra.util.datastruct.LongArray;
public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest { public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest {
private static Schema SCHEMA = 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 ProgramDB program;
private AddressSpace space; private AddressSpace space;
@@ -114,7 +114,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator0() throws Exception { public void testIterator0() throws Exception {
AddressKeyIterator it = new AddressKeyIterator(); AddressKeyIterator it = new AddressKeyIterator();
assertTrue(!it.hasNext()); assertTrue(!it.hasNext());
assertTrue(!it.hasPrevious()); assertTrue(!it.hasPrevious());
@@ -133,7 +133,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator1() throws Exception { public void testIterator1() throws Exception {
int index = 0; int index = 0;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, true); AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, true);
while (it.hasNext()) { while (it.hasNext()) {
@@ -144,7 +144,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator2() throws Exception { public void testIterator2() throws Exception {
int index = 0x10; int index = 0x10;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x4000), true); AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x4000), true);
while (it.hasNext()) { while (it.hasNext()) {
@@ -155,7 +155,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator3() throws Exception { public void testIterator3() throws Exception {
int index = 0x11; int index = 0x11;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), false); AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), false);
while (it.hasNext()) { while (it.hasNext()) {
@@ -166,7 +166,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator4() throws Exception { public void testIterator4() throws Exception {
int index = 0x10; int index = 0x10;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), true); AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), true);
while (it.hasNext()) { while (it.hasNext()) {
@@ -177,7 +177,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator5() throws Exception { public void testIterator5() throws Exception {
int index = 0x0f; int index = 0x0f;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), true); AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), true);
while (it.hasPrevious()) { while (it.hasPrevious()) {
@@ -188,7 +188,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator6() throws Exception { public void testIterator6() throws Exception {
int index = 0x10; int index = 0x10;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), false); AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), false);
while (it.hasPrevious()) { while (it.hasPrevious()) {
@@ -199,7 +199,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator7() throws Exception { public void testIterator7() throws Exception {
AddressSet set = new AddressSet(); AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008)); set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000)); set.addRange(addr(0x9008), addr(0x10000));
@@ -216,7 +216,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator8() throws Exception { public void testIterator8() throws Exception {
AddressSet set = new AddressSet(); AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008)); set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000)); set.addRange(addr(0x9008), addr(0x10000));
@@ -233,7 +233,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator9() throws Exception { public void testIterator9() throws Exception {
AddressSet set = new AddressSet(); AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008)); set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000)); set.addRange(addr(0x9008), addr(0x10000));
@@ -247,7 +247,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator10() throws Exception { public void testIterator10() throws Exception {
AddressSet set = new AddressSet(); AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008)); set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000)); set.addRange(addr(0x9008), addr(0x10000));
@@ -264,7 +264,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator11() throws Exception { public void testIterator11() throws Exception {
AddressSet set = new AddressSet(); AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008)); set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000)); set.addRange(addr(0x9008), addr(0x10000));
@@ -279,7 +279,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator12() throws Exception { public void testIterator12() throws Exception {
int index = 0x3f; int index = 0x3f;
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, null, false); AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, null, false);
while (it.hasPrevious()) { while (it.hasPrevious()) {
@@ -290,7 +290,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIterator13() throws Exception { public void testIterator13() throws Exception {
AddressSet set = new AddressSet(); AddressSet set = new AddressSet();
set.addRange(addr(0x3008), addr(0x5008)); set.addRange(addr(0x3008), addr(0x5008));
set.addRange(addr(0x9008), addr(0x10000)); set.addRange(addr(0x9008), addr(0x10000));
@@ -304,7 +304,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIteratorCheckWrap1() throws Exception { public void testIteratorCheckWrap1() throws Exception {
addRecord(addr(0x0)); addRecord(addr(0x0));
addRecord(addr(0x0100)); addRecord(addr(0x0100));
@@ -320,7 +320,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
} }
@Test @Test
public void testIteratorCheckWrap2() throws Exception { public void testIteratorCheckWrap2() throws Exception {
addRecord(addr(0x0)); addRecord(addr(0x0));
addRecord(addr(0x0100)); addRecord(addr(0x0100));
@@ -35,7 +35,8 @@ import ghidra.util.Lock;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitorAdapter; 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 TestEnv env; // needed to discover languages
private ProgramDB program; private ProgramDB program;
@@ -83,7 +84,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
public void testTransaction() { public void testTransaction() {
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap, AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
new Lock("Test"), "TEST", this, LongField.class, true); new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
try { try {
map.paintRange(addr(0), addr(0x1000), ONE); map.paintRange(addr(0), addr(0x1000), ONE);
@@ -114,7 +115,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
public void testPaint() { public void testPaint() {
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap, 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"); int id = program.startTransaction("TEST");
try { try {
@@ -152,7 +153,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
public void testClear() { public void testClear() {
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap, 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"); int id = program.startTransaction("TEST");
try { try {
@@ -186,7 +187,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
public void testAddressRangeIterator() { public void testAddressRangeIterator() {
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap, 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"); int id = program.startTransaction("TEST");
try { try {
@@ -248,7 +249,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
public void testMove() { public void testMove() {
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap, 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"); int id = program.startTransaction("TEST");
try { try {
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,31 +20,29 @@ import java.util.Random;
import db.*; import db.*;
public class DatabaseBenchMarks { public class DatabaseBenchMarks {
static int BUFFER_SIZE = 16*1024; static int BUFFER_SIZE = 16 * 1024;
static int CACHE_SIZE = 32*1024*1024; static int CACHE_SIZE = 32 * 1024 * 1024;
public static void main(String[] args) { public static void main(String[] args) {
TestTimer timer = new TestTimer(); TestTimer timer = new TestTimer();
testOrderedIntInsertions(timer,1000); testOrderedIntInsertions(timer, 1000);
testOrderedIntInsertions(timer,10000); testOrderedIntInsertions(timer, 10000);
testOrderedIntInsertions(timer,100000); testOrderedIntInsertions(timer, 100000);
testOrderedIntInsertions(timer,1000000); testOrderedIntInsertions(timer, 1000000);
System.out.println(""); System.out.println("");
testOrderedStringInsertions(timer,1000); testOrderedStringInsertions(timer, 1000);
testOrderedStringInsertions(timer,10000); testOrderedStringInsertions(timer, 10000);
testOrderedStringInsertions(timer,100000); testOrderedStringInsertions(timer, 100000);
testOrderedStringInsertions(timer,1000000); testOrderedStringInsertions(timer, 1000000);
System.out.println(""); System.out.println("");
testRandomIntInsertions(timer,1000); testRandomIntInsertions(timer, 1000);
testRandomIntInsertions(timer,10000); testRandomIntInsertions(timer, 10000);
testRandomIntInsertions(timer,100000); testRandomIntInsertions(timer, 100000);
testRandomIntInsertions(timer,1000000); testRandomIntInsertions(timer, 1000000);
System.out.println(""); System.out.println("");
testIteration(timer); testIteration(timer);
@@ -54,23 +51,27 @@ public class DatabaseBenchMarks {
testRandomAccess(timer); testRandomAccess(timer);
} }
private static void testOrderedIntInsertions(TestTimer timer, int numInsertions) { private static void testOrderedIntInsertions(TestTimer timer, int numInsertions) {
try { try {
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE); DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
long transactionID = dbh.startTransaction(); 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); Table table = dbh.createTable("Test", schema);
Record record = schema.createRecord(0); Record record = schema.createRecord(0);
timer.start("Inserting "+numInsertions+" sorted records with long keys and integer values"); timer.start(
for(int i=0;i<numInsertions;i++) { "Inserting " + numInsertions + " sorted records with long keys and integer values");
for (int i = 0; i < numInsertions; i++) {
record.setKey(i); record.setKey(i);
record.setIntValue(0,i); record.setIntValue(0, i);
table.putRecord(record); table.putRecord(record);
} }
timer.end(); timer.end();
dbh.endTransaction(transactionID, true); dbh.endTransaction(transactionID, true);
dbh.close(); dbh.close();
}catch(IOException e) { }
catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@@ -79,19 +80,22 @@ public class DatabaseBenchMarks {
try { try {
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE); DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
long transactionID = dbh.startTransaction(); 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); Table table = dbh.createTable("Test", schema);
Record record = schema.createRecord(0); Record record = schema.createRecord(0);
timer.start("Inserting "+numInsertions+" sorted records with long keys and String (length = 8) values"); timer.start("Inserting " + numInsertions +
for(int i=0;i<numInsertions;i++) { " sorted records with long keys and String (length = 8) values");
for (int i = 0; i < numInsertions; i++) {
record.setKey(i); record.setKey(i);
record.setString(0,"abcdefgh"); record.setString(0, "abcdefgh");
table.putRecord(record); table.putRecord(record);
} }
timer.end(); timer.end();
dbh.endTransaction(transactionID, true); dbh.endTransaction(transactionID, true);
dbh.close(); dbh.close();
}catch(IOException e) { }
catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@@ -101,19 +105,22 @@ public class DatabaseBenchMarks {
Random random = new Random(); Random random = new Random();
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE); DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
long transactionID = dbh.startTransaction(); 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); Table table = dbh.createTable("Test", schema);
Record record = schema.createRecord(0); Record record = schema.createRecord(0);
timer.start("Inserting "+numInsertions+" random records with long keys and integer values"); timer.start(
for(int i=0;i<numInsertions;i++) { "Inserting " + numInsertions + " random records with long keys and integer values");
for (int i = 0; i < numInsertions; i++) {
record.setKey(random.nextLong()); record.setKey(random.nextLong());
record.setIntValue(0,i); record.setIntValue(0, i);
table.putRecord(record); table.putRecord(record);
} }
timer.end(); timer.end();
dbh.endTransaction(transactionID, true); dbh.endTransaction(transactionID, true);
dbh.close(); dbh.close();
}catch(IOException e) { }
catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@@ -122,25 +129,27 @@ public class DatabaseBenchMarks {
try { try {
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE); DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
long transactionID = dbh.startTransaction(); 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); Table table = dbh.createTable("Test", schema);
Record record = schema.createRecord(0); Record record = schema.createRecord(0);
System.out.print("building database..."); System.out.print("building database...");
for(int i=0;i<1000000;i++) { for (int i = 0; i < 1000000; i++) {
record.setKey(i); record.setKey(i);
record.setIntValue(0,i); record.setIntValue(0, i);
table.putRecord(record); table.putRecord(record);
} }
timer.start("Iterating over 1000000 int records"); timer.start("Iterating over 1000000 int records");
RecordIterator it = table.iterator(); RecordIterator it = table.iterator();
while(it.hasNext()) { while (it.hasNext()) {
it.next(); it.next();
} }
timer.end(); timer.end();
dbh.endTransaction(transactionID, true); dbh.endTransaction(transactionID, true);
dbh.close(); dbh.close();
}catch(IOException e) { }
catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@@ -149,42 +158,43 @@ public class DatabaseBenchMarks {
try { try {
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE); DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
long transactionID = dbh.startTransaction(); 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); Table table = dbh.createTable("Test", schema);
Record record = schema.createRecord(0); Record record = schema.createRecord(0);
System.out.print("building database..."); System.out.print("building database...");
for(int i=0;i<1000000;i++) { for (int i = 0; i < 1000000; i++) {
record.setKey(i); record.setKey(i);
record.setIntValue(0,i); record.setIntValue(0, i);
table.putRecord(record); table.putRecord(record);
} }
Random random = new Random(); Random random = new Random();
timer.start("Randomly accessing 1000000 int records"); 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)); table.getRecord(random.nextInt(1000000));
} }
timer.end(); timer.end();
dbh.endTransaction(transactionID, true); dbh.endTransaction(transactionID, true);
dbh.close(); dbh.close();
}catch(IOException e) { }
catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
class TestTimer { class TestTimer {
long start; long start;
void start(String testMsg) { void start(String testMsg) {
System.out.print(testMsg+"... "); System.out.print(testMsg + "... ");
start = System.currentTimeMillis(); start = System.currentTimeMillis();
} }
void end() { void end() {
long end = System.currentTimeMillis(); 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; static final int CACHE_SIZE = 10000;
// @formatter:off // @formatter:off
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Function ID", new Class[] { static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Function ID", new Field[] {
ShortField.class, LongField.class, ShortField.INSTANCE, LongField.INSTANCE,
ByteField.class, LongField.class, LongField.class, ByteField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE,
LongField.class, LongField.class, LongField.class, LongField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE,
ByteField.class ByteField.INSTANCE
}, new String[] { }, new String[] {
"Code Unit Size", "Full Hash", "Code Unit Size", "Full Hash",
"Specific Hash Additional Size", "Specific Hash", "Library ID", "Specific Hash Additional Size", "Specific Hash", "Library ID",
@@ -133,14 +133,15 @@ public class FunctionsTable {
*/ */
public List<FunctionRecord> getFunctionRecordsByFullHash(long hash) throws IOException { public List<FunctionRecord> getFunctionRecordsByFullHash(long hash) throws IOException {
LongField hashField = new LongField(hash); 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()) { if (!iterator.hasNext()) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<FunctionRecord> list = new ArrayList<>(); List<FunctionRecord> list = new ArrayList<>();
while (iterator.hasNext()) { while (iterator.hasNext()) {
long key = iterator.next(); Field key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key); FunctionRecord functionRecord = functionCache.get(key.getLongValue());
if (functionRecord == null) { if (functionRecord == null) {
Record record = table.getRecord(key); Record record = table.getRecord(key);
functionRecord = new FunctionRecord(fidDb, functionCache, record); functionRecord = new FunctionRecord(fidDb, functionCache, record);
@@ -216,15 +217,15 @@ public class FunctionsTable {
*/ */
public List<FunctionRecord> getFunctionRecordsByNameSubstring(String nameSearch) public List<FunctionRecord> getFunctionRecordsByNameSubstring(String nameSearch)
throws IOException { throws IOException {
DBLongIterator iterator = table.indexKeyIterator(NAME_ID_COL); DBFieldIterator iterator = table.indexKeyIterator(NAME_ID_COL);
if (!iterator.hasNext()) { if (!iterator.hasNext()) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<FunctionRecord> list = new ArrayList<>(); List<FunctionRecord> list = new ArrayList<>();
while (iterator.hasNext()) { while (iterator.hasNext()) {
long key = iterator.next(); Field key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key); FunctionRecord functionRecord = functionCache.get(key.getLongValue());
if (functionRecord == null) { if (functionRecord == null) {
Record record = table.getRecord(key); Record record = table.getRecord(key);
long nameID = record.getLongValue(NAME_ID_COL); long nameID = record.getLongValue(NAME_ID_COL);
@@ -255,15 +256,15 @@ public class FunctionsTable {
*/ */
public List<FunctionRecord> getFunctionRecordsByNameRegex(String regex) throws IOException { public List<FunctionRecord> getFunctionRecordsByNameRegex(String regex) throws IOException {
Matcher matcher = Pattern.compile(regex).matcher(""); Matcher matcher = Pattern.compile(regex).matcher("");
DBLongIterator iterator = table.indexKeyIterator(NAME_ID_COL); DBFieldIterator iterator = table.indexKeyIterator(NAME_ID_COL);
if (!iterator.hasNext()) { if (!iterator.hasNext()) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<FunctionRecord> list = new ArrayList<>(); List<FunctionRecord> list = new ArrayList<>();
while (iterator.hasNext()) { while (iterator.hasNext()) {
long key = iterator.next(); Field key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key); FunctionRecord functionRecord = functionCache.get(key.getLongValue());
if (functionRecord == null) { if (functionRecord == null) {
Record record = table.getRecord(key); Record record = table.getRecord(key);
long nameID = record.getLongValue(NAME_ID_COL); long nameID = record.getLongValue(NAME_ID_COL);
@@ -347,15 +348,15 @@ public class FunctionsTable {
return Collections.emptyList(); return Collections.emptyList();
} }
LongField field = new LongField(stringID); 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()) { if (!iterator.hasNext()) {
return Collections.emptyList(); return Collections.emptyList();
} }
final long libraryKey = library.getLibraryID(); final long libraryKey = library.getLibraryID();
List<FunctionRecord> list = new ArrayList<>(); List<FunctionRecord> list = new ArrayList<>();
while (iterator.hasNext()) { while (iterator.hasNext()) {
long key = iterator.next(); Field key = iterator.next();
FunctionRecord functionRecord = functionCache.get(key); FunctionRecord functionRecord = functionCache.get(key.getLongValue());
if (functionRecord == null) { if (functionRecord == null) {
Record record = table.getRecord(key); Record record = table.getRecord(key);
if (record.getLongValue(LIBRARY_ID_COL) == libraryKey) { if (record.getLongValue(LIBRARY_ID_COL) == libraryKey) {
@@ -49,10 +49,10 @@ public class LibrariesTable {
static final int GHIDRA_COMPILER_SPEC_ID_COL = 7; static final int GHIDRA_COMPILER_SPEC_ID_COL = 7;
// @formatter:off // @formatter:off
static final Schema SCHEMA = new Schema(VERSION, "Library ID", new Class[] { static final Schema SCHEMA = new Schema(VERSION, "Library ID", new Field[] {
StringField.class, StringField.class, StringField.class, StringField.INSTANCE, StringField.INSTANCE, StringField.INSTANCE,
StringField.class, StringField.class, IntField.class, IntField.class, StringField.INSTANCE, StringField.INSTANCE, IntField.INSTANCE, IntField.INSTANCE,
StringField.class StringField.INSTANCE
}, new String[] { }, new String[] {
"Library Family Name", "Library Version", "Library Variant", "Library Family Name", "Library Version", "Library Variant",
"Ghidra Version", "Ghidra Language ID", "Ghidra Language Version", "Ghidra Language Minor Version", "Ghidra Version", "Ghidra Language ID", "Ghidra Language Version", "Ghidra Language Minor Version",
@@ -90,8 +90,9 @@ public class LibrariesTable {
if (libraryVersion != VERSION) { if (libraryVersion != VERSION) {
String msg = "Expected version " + VERSION + " for table " + LIBRARIES_TABLE + String msg = "Expected version " + VERSION + " for table " + LIBRARIES_TABLE +
" but got " + table.getSchema().getVersion(); " but got " + table.getSchema().getVersion();
throw new VersionException(msg, libraryVersion < VERSION throw new VersionException(msg,
? VersionException.OLDER_VERSION : VersionException.NEWER_VERSION, libraryVersion < VERSION ? VersionException.OLDER_VERSION
: VersionException.NEWER_VERSION,
false); false);
} }
} }
@@ -155,14 +156,14 @@ public class LibrariesTable {
public List<LibraryRecord> getLibrariesByName(String name, String version, String variant) public List<LibraryRecord> getLibrariesByName(String name, String version, String variant)
throws IOException { throws IOException {
StringField hashField = new StringField(name); StringField hashField = new StringField(name);
DBLongIterator iterator = DBFieldIterator iterator =
table.indexKeyIterator(LIBRARY_FAMILY_NAME_COL, hashField, hashField, true); table.indexKeyIterator(LIBRARY_FAMILY_NAME_COL, hashField, hashField, true);
if (!iterator.hasNext()) { if (!iterator.hasNext()) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<LibraryRecord> list = new ArrayList<LibraryRecord>(); List<LibraryRecord> list = new ArrayList<LibraryRecord>();
while (iterator.hasNext()) { while (iterator.hasNext()) {
long key = iterator.next(); Field key = iterator.next();
Record record = table.getRecord(key); Record record = table.getRecord(key);
LibraryRecord libraryRecord = new LibraryRecord(record); LibraryRecord libraryRecord = new LibraryRecord(record);
if (version != null) { if (version != null) {
@@ -27,8 +27,8 @@ public class RelationsTable {
// static final int CACHE_SIZE = 10000; // static final int CACHE_SIZE = 10000;
// @formatter:off // @formatter:off
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Relation Smash", new Class[] { static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Relation Smash",
}, new String[] { new Field[] { }, new String[] {
}); });
// @formatter:on // @formatter:on
@@ -35,11 +35,9 @@ public class StringsTable {
static final int CACHE_SIZE = 10000; static final int CACHE_SIZE = 10000;
// @formatter:off // @formatter:off
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "String ID", new Class[] { static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "String ID",
StringField.class new Field[] { StringField.INSTANCE },
}, new String[] { new String[] { "String Value" });
"String Value"
});
// @formatter:on // @formatter:on
static int[] INDEXED_COLUMNS = new int[] { STRING_VALUE_COL }; static int[] INDEXED_COLUMNS = new int[] { STRING_VALUE_COL };
@@ -69,29 +67,30 @@ public class StringsTable {
* @throws IOException if the database has a problem * @throws IOException if the database has a problem
*/ */
long obtainStringID(String value) throws IOException { 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) { if (records == null || records.length == 0) {
// create // create
Record record = SCHEMA.createRecord(UniversalIdGenerator.nextID().getValue()); long key = UniversalIdGenerator.nextID().getValue();
Record record = SCHEMA.createRecord(key);
record.setString(STRING_VALUE_COL, value); record.setString(STRING_VALUE_COL, value);
table.putRecord(record); table.putRecord(record);
return record.getKey(); return key;
} }
return records[0]; return records[0].getLongValue();
} }
/** /**
* Lookup existing ID or return null for String value. * Lookup existing ID or return null for String value.
* @param value the 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 * @throws IOException if the database has a problem
*/ */
Long lookupStringID(String value) throws IOException { 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) { if (records == null || records.length == 0) {
return null; return null;
} }
return records[0]; return records[0].getLongValue();
} }
/** /**
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,32 +13,32 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package db.util; package ghidra.feature.vt.api.db;
import db.Field; import db.Field;
public class TableColumn { public class TableColumn {
private final Class<? extends Field> columnClass; private final Field columnField;
private boolean indexed; private boolean indexed;
private int ordinal; private int ordinal;
private String name; private String name;
public TableColumn( Class<? extends Field> columnClass ) { public TableColumn(Field columnField) {
this( columnClass, false ); this(columnField, false);
} }
public TableColumn( Class<? extends Field> columnClass, boolean isIndexed ) { public TableColumn(Field columnField, boolean isIndexed) {
this.columnClass = columnClass; this.columnField = columnField;
indexed = isIndexed; indexed = isIndexed;
} }
void setName( String name ) { void setName(String name) {
this.name = name; this.name = name;
} }
void setOrdinal( int ordinal ) { void setOrdinal(int ordinal) {
this.ordinal = ordinal; this.ordinal = ordinal;
} }
@@ -47,8 +46,8 @@ public class TableColumn {
return indexed; return indexed;
} }
public Class<? extends Field> getColumnClass() { public Field getColumnField() {
return columnClass; return columnField;
} }
public String name() { public String name() {
@@ -61,6 +60,6 @@ public class TableColumn {
@Override @Override
public String toString() { public String toString() {
return name() + "("+ ordinal +")"; return name() + "(" + ordinal + ")";
} }
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,13 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package db.util; package ghidra.feature.vt.api.db;
import ghidra.util.Msg;
import java.util.*; import java.util.*;
import db.Field; import db.Field;
import ghidra.util.Msg;
public class TableDescriptor { public class TableDescriptor {
@@ -32,45 +30,44 @@ public class TableDescriptor {
private TableColumn[] discoverTableColumns() { private TableColumn[] discoverTableColumns() {
Class<? extends TableDescriptor> clazz = getClass(); Class<? extends TableDescriptor> clazz = getClass();
java.lang.reflect.Field[] fields = clazz.getFields(); java.lang.reflect.Field[] fields = clazz.getFields();
List<TableColumn> list = new ArrayList<TableColumn>(fields.length); 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(); Class<?> type = field.getType();
if ( !TableColumn.class.isAssignableFrom( type ) ) { if (!TableColumn.class.isAssignableFrom(type)) {
continue; continue;
} }
try { try {
TableColumn column = (TableColumn) field.get( null ); TableColumn column = (TableColumn) field.get(null);
column.setName( field.getName() ); column.setName(field.getName());
column.setOrdinal( list.size() ); column.setOrdinal(list.size());
list.add( column ); list.add(column);
} }
catch ( IllegalArgumentException e ) { catch (IllegalArgumentException e) {
// shouldn't happen // shouldn't happen
} }
catch ( IllegalAccessException e ) { catch (IllegalAccessException e) {
Msg.showError( this, null, "Class Usage Error", "You must provide public " + Msg.showError(this, null, "Class Usage Error",
"static members for your TableColumns" ); "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() { public int[] getIndexedColumns() {
int count = 0; int count = 0;
for ( TableColumn column : columns ) { for (TableColumn column : columns) {
if (column.isIndexed()) { if (column.isIndexed()) {
count++; count++;
} }
} }
int[] indexedColumns = new int[count]; int[] indexedColumns = new int[count];
count = 0; count = 0;
for ( TableColumn column : columns ) { for (TableColumn column : columns) {
if (column.isIndexed()) { if (column.isIndexed()) {
indexedColumns[count++] = column.column(); indexedColumns[count++] = column.column();
} }
@@ -80,19 +77,18 @@ public class TableDescriptor {
public String[] getColumnNames() { public String[] getColumnNames() {
List<String> list = new LinkedList<String>(); List<String> list = new LinkedList<String>();
for ( TableColumn column : columns ) { for (TableColumn column : columns) {
list.add( column.name() ); 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 Field[] getColumnFields() {
public Class<? extends Field>[] getColumnClasses() { Field[] fields = new Field[columns.length];
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>(); for (int i = 0; i < fields.length; i++) {
for ( TableColumn column : columns ) { fields[i] = columns[i].getColumnField().newField();
list.add( column.getColumnClass() );
} }
return list.toArray( new Class[ columns.length ] ); return fields;
} }
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,31 +16,32 @@
package ghidra.feature.vt.api.db; package ghidra.feature.vt.api.db;
import static ghidra.feature.vt.api.db.VTAddressCorrelatorAdapter.AddressCorrelationTableDescriptor.*; 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.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import db.*; 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 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_ENTRY_COL = new TableColumn(LongField.INSTANCE, true);
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.class); public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.class); public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
public static AddressCorrelationTableDescriptor INSTANCE = new AddressCorrelationTableDescriptor(); public static AddressCorrelationTableDescriptor INSTANCE =
new AddressCorrelationTableDescriptor();
} }
static String TABLE_NAME = "AddressCorrelationTable"; static String TABLE_NAME = "AddressCorrelationTable";
static Schema TABLE_SCHEMA = new Schema(0, "Key", static Schema TABLE_SCHEMA =
INSTANCE.getColumnClasses(), INSTANCE.getColumnNames()); new Schema(0, "Key", INSTANCE.getColumnFields(), INSTANCE.getColumnNames());
static int[] TABLE_INDEXES = INSTANCE.getIndexedColumns(); static int[] TABLE_INDEXES = INSTANCE.getIndexedColumns();
private DBHandle dbHandle; private DBHandle dbHandle;
@@ -58,7 +58,8 @@ public abstract class VTAddressCorrelatorAdapter {
return new VTAddressCorrelationAdapterV0(dbHandle, monitor); 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; abstract List<Record> getAddressRecords(long sourceEntryLong) throws IOException;
@@ -69,6 +70,7 @@ public abstract class VTAddressCorrelatorAdapter {
void save(TaskMonitor monitor) throws CancelledException, IOException { void save(TaskMonitor monitor) throws CancelledException, IOException {
dbHandle.save("", null, monitor); dbHandle.save("", null, monitor);
} }
void saveAs(File file, TaskMonitor monitor) throws CancelledException, IOException { void saveAs(File file, TaskMonitor monitor) throws CancelledException, IOException {
dbHandle.saveAs(file, true, monitor); dbHandle.saveAs(file, true, monitor);
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,35 +15,36 @@
*/ */
package ghidra.feature.vt.api.db; package ghidra.feature.vt.api.db;
import static ghidra.feature.vt.api.db.VTAssociationTableDBAdapter.AssociationTableDescriptor.INSTANCE; import static ghidra.feature.vt.api.db.VTAssociationTableDBAdapter.AssociationTableDescriptor.*;
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 java.io.IOException; import java.io.IOException;
import java.util.Set; import java.util.Set;
import db.*; 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 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 SOURCE_ADDRESS_COL = new TableColumn(LongField.INSTANCE, true);
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.class, true); public static TableColumn DESTINATION_ADDRESS_COL =
public static TableColumn TYPE_COL = new TableColumn(ByteField.class); new TableColumn(LongField.INSTANCE, true);
public static TableColumn STATUS_COL = new TableColumn(ByteField.class); public static TableColumn TYPE_COL = new TableColumn(ByteField.INSTANCE);
public static TableColumn APPLIED_STATUS_COL = new TableColumn(ByteField.class); public static TableColumn STATUS_COL = new TableColumn(ByteField.INSTANCE);
public static TableColumn VOTE_COUNT_COL = new TableColumn(IntField.class); 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(); public static AssociationTableDescriptor INSTANCE = new AssociationTableDescriptor();
} }
static String TABLE_NAME = "AssociationTable"; static String TABLE_NAME = "AssociationTable";
static Schema TABLE_SCHEMA = 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(); static int[] TABLE_INDEXES = INSTANCE.getIndexedColumns();
public static VTAssociationTableDBAdapter createAdapter(DBHandle dbHandle) throws IOException { public static VTAssociationTableDBAdapter createAdapter(DBHandle dbHandle) throws IOException {
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,36 +15,35 @@
*/ */
package ghidra.feature.vt.api.db; package ghidra.feature.vt.api.db;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.INSTANCE; import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.*;
import ghidra.feature.vt.api.impl.MarkupItemStorage;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException; import java.io.IOException;
import db.*; 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 abstract class VTMatchMarkupItemTableDBAdapter {
public static class MarkupTableDescriptor extends db.util.TableDescriptor { public static class MarkupTableDescriptor extends ghidra.feature.vt.api.db.TableDescriptor {
public static TableColumn ASSOCIATION_KEY_COL = new TableColumn(LongField.class, true); public static TableColumn ASSOCIATION_KEY_COL = new TableColumn(LongField.INSTANCE, true);
public static TableColumn ADDRESS_SOURCE_COL = new TableColumn(StringField.class); public static TableColumn ADDRESS_SOURCE_COL = new TableColumn(StringField.INSTANCE);
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.class); public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
public static TableColumn MARKUP_TYPE_COL = new TableColumn(ShortField.class); public static TableColumn MARKUP_TYPE_COL = new TableColumn(ShortField.INSTANCE);
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.class); public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
public static TableColumn SOURCE_VALUE_COL = new TableColumn(StringField.class); public static TableColumn SOURCE_VALUE_COL = new TableColumn(StringField.INSTANCE);
public static TableColumn ORIGINAL_DESTINATION_VALUE_COL = public static TableColumn ORIGINAL_DESTINATION_VALUE_COL =
new TableColumn(StringField.class); new TableColumn(StringField.INSTANCE);
public static TableColumn STATUS_COL = new TableColumn(ByteField.class); public static TableColumn STATUS_COL = new TableColumn(ByteField.INSTANCE);
public static TableColumn STATUS_DESCRIPTION_COL = new TableColumn(StringField.class); public static TableColumn STATUS_DESCRIPTION_COL = new TableColumn(StringField.INSTANCE);
public static MarkupTableDescriptor INSTANCE = new MarkupTableDescriptor(); public static MarkupTableDescriptor INSTANCE = new MarkupTableDescriptor();
} }
protected static String TABLE_NAME = "MatchMarkupItemTable"; protected static String TABLE_NAME = "MatchMarkupItemTable";
static Schema TABLE_SCHEMA = 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(); protected static int[] INDEXED_COLUMNS = INSTANCE.getIndexedColumns();
@@ -71,6 +69,5 @@ public abstract class VTMatchMarkupItemTableDBAdapter {
public abstract int getRecordCount(); public abstract int getRecordCount();
public abstract Record createMarkupItemRecord(MarkupItemStorage markupItem) public abstract Record createMarkupItemRecord(MarkupItemStorage markupItem) throws IOException;
throws IOException;
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,34 +15,33 @@
*/ */
package ghidra.feature.vt.api.db; 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.feature.vt.api.main.VTProgramCorrelator;
import ghidra.program.database.map.AddressMap; import ghidra.program.database.map.AddressMap;
import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSet;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import db.*;
public abstract class VTMatchSetTableDBAdapter { public abstract class VTMatchSetTableDBAdapter {
public enum ColumnDescription { public enum ColumnDescription {
CORRELATOR_CLASS_COL(StringField.class), CORRELATOR_CLASS_COL(StringField.INSTANCE),
CORRELATOR_NAME_COL(StringField.class), CORRELATOR_NAME_COL(StringField.INSTANCE),
OPTIONS_COL(StringField.class); OPTIONS_COL(StringField.INSTANCE);
private final Class<? extends Field> columnClass; private final Field columnField;
private ColumnDescription(Class<? extends Field> columnClass) { private ColumnDescription(Field columnField) {
this.columnClass = columnClass; this.columnField = columnField;
} }
public Class<? extends Field> getColumnClass() { public Field getColumnField() {
return columnClass; return columnField;
} }
public int column() { public int column() {
@@ -59,20 +57,18 @@ public abstract class VTMatchSetTableDBAdapter {
return list.toArray(new String[columns.length]); return list.toArray(new String[columns.length]);
} }
@SuppressWarnings("unchecked") private static Field[] getColumnFields() {
// we know our class types are safe
private static Class<? extends Field>[] getColumnClasses() {
ColumnDescription[] columns = ColumnDescription.values(); ColumnDescription[] columns = ColumnDescription.values();
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>(); Field[] fields = new Field[columns.length];
for (ColumnDescription column : columns) { for (int i = 0; i < fields.length; i++) {
list.add(column.getColumnClass()); fields[i] = columns[i].getColumnField();
} }
return list.toArray(new Class[columns.length]); return fields;
} }
} }
static String TABLE_NAME = "MatchSetTable"; 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()); ColumnDescription.getColumnNames());
static VTMatchSetTableDBAdapter createAdapter(DBHandle dbHandle) throws IOException { static VTMatchSetTableDBAdapter createAdapter(DBHandle dbHandle) throws IOException {
@@ -16,13 +16,6 @@
package ghidra.feature.vt.api.db; package ghidra.feature.vt.api.db;
import static ghidra.feature.vt.api.db.VTMatchSetTableDBAdapter.ColumnDescription.*; 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.IOException;
import java.io.StringWriter; import java.io.StringWriter;
@@ -31,13 +24,20 @@ import org.jdom.Element;
import org.jdom.output.XMLOutputter; import org.jdom.output.XMLOutputter;
import db.*; 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 { public class VTMatchSetTableDBAdapterV0 extends VTMatchSetTableDBAdapter {
private Table table; private Table table;
private static final Schema STORED_ADDRESS_RANGE_SCHEMA = new Schema(0, "Key", new Class[] { private static final Schema STORED_ADDRESS_RANGE_SCHEMA = new Schema(0, "Key",
LongField.class, LongField.class }, new String[] { "addr1", "addr2" }); new Field[] { LongField.INSTANCE, LongField.INSTANCE }, new String[] { "addr1", "addr2" });
private final DBHandle dbHandle; private final DBHandle dbHandle;
@@ -46,7 +46,8 @@ public class VTMatchSetTableDBAdapterV0 extends VTMatchSetTableDBAdapter {
table = dbHandle.createTable(TABLE_NAME, TABLE_SCHEMA); 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; this.dbHandle = dbHandle;
table = dbHandle.getTable(TABLE_NAME); table = dbHandle.getTable(TABLE_NAME);
if (table == null) { if (table == null) {
@@ -59,7 +60,8 @@ public class VTMatchSetTableDBAdapterV0 extends VTMatchSetTableDBAdapter {
} }
@Override @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 record = TABLE_SCHEMA.createRecord(key);
record.setString(CORRELATOR_CLASS_COL.column(), correlator.getClass().getName()); record.setString(CORRELATOR_CLASS_COL.column(), correlator.getClass().getName());
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,36 +15,35 @@
*/ */
package ghidra.feature.vt.api.db; 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.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import db.*; import db.*;
import ghidra.feature.vt.api.main.VTMatchInfo;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public abstract class VTMatchTableDBAdapter { public abstract class VTMatchTableDBAdapter {
public enum ColumnDescription { public enum ColumnDescription {
TAG_KEY_COL(LongField.class), TAG_KEY_COL(LongField.INSTANCE),
MATCH_SET_COL(LongField.class), MATCH_SET_COL(LongField.INSTANCE),
SIMILARITY_SCORE_COL(StringField.class), SIMILARITY_SCORE_COL(StringField.INSTANCE),
CONFIDENCE_SCORE_COL(StringField.class), CONFIDENCE_SCORE_COL(StringField.INSTANCE),
LENGTH_TYPE(StringField.class), LENGTH_TYPE(StringField.INSTANCE),
SOURCE_LENGTH_COL(IntField.class), SOURCE_LENGTH_COL(IntField.INSTANCE),
DESTINATION_LENGTH_COL(IntField.class), DESTINATION_LENGTH_COL(IntField.INSTANCE),
ASSOCIATION_COL(LongField.class); ASSOCIATION_COL(LongField.INSTANCE);
private final Class<? extends Field> columnClass; private final Field columnField;
private ColumnDescription(Class<? extends Field> columnClass) { private ColumnDescription(Field columnField) {
this.columnClass = columnClass; this.columnField = columnField;
} }
public Class<? extends Field> getColumnClass() { public Field getColumnField() {
return columnClass; return columnField;
} }
public int column() { public int column() {
@@ -61,22 +59,19 @@ public abstract class VTMatchTableDBAdapter {
return list.toArray(new String[columns.length]); return list.toArray(new String[columns.length]);
} }
@SuppressWarnings("unchecked") private static Field[] getColumnFields() {
// we know our class types are safe
private static Class<? extends Field>[] getColumnClasses() {
ColumnDescription[] columns = ColumnDescription.values(); ColumnDescription[] columns = ColumnDescription.values();
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>(); Field[] fields = new Field[columns.length];
for (ColumnDescription column : columns) { for (int i = 0; i < fields.length; i++) {
list.add(column.getColumnClass()); fields[i] = columns[i].getColumnField();
} }
return list.toArray(new Class[columns.length]); return fields;
} }
} }
static String TABLE_NAME = "MatchTable"; static String TABLE_NAME = "MatchTable";
static Schema TABLE_SCHEMA = static Schema TABLE_SCHEMA = new Schema(0, "Key", ColumnDescription.getColumnFields(),
new Schema(0, "Key", ColumnDescription.getColumnClasses(), ColumnDescription.getColumnNames());
ColumnDescription.getColumnNames());
static VTMatchTableDBAdapter createAdapter(DBHandle dbHandle, long tableID) throws IOException { static VTMatchTableDBAdapter createAdapter(DBHandle dbHandle, long tableID) throws IOException {
return new VTMatchTableDBAdapterV0(dbHandle, tableID); return new VTMatchTableDBAdapterV0(dbHandle, tableID);
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,14 +15,13 @@
*/ */
package ghidra.feature.vt.api.db; package ghidra.feature.vt.api.db;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import db.*; 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. * Abstract adapter for the database table that holds tags for version tracking matches.
@@ -31,16 +29,16 @@ import db.*;
public abstract class VTMatchTagDBAdapter { public abstract class VTMatchTagDBAdapter {
public enum ColumnDescription { 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) { private ColumnDescription(Field columnField) {
this.columnClass = columnClass; this.columnField = columnField;
} }
public Class<? extends Field> getColumnClass() { public Field getColumnField() {
return columnClass; return columnField;
} }
public int column() { public int column() {
@@ -56,22 +54,19 @@ public abstract class VTMatchTagDBAdapter {
return list.toArray(new String[columns.length]); return list.toArray(new String[columns.length]);
} }
@SuppressWarnings("unchecked") private static Field[] getColumnFields() {
// we know our class types are safe
private static Class<? extends Field>[] getColumnClasses() {
ColumnDescription[] columns = ColumnDescription.values(); ColumnDescription[] columns = ColumnDescription.values();
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>(); Field[] fields = new Field[columns.length];
for (ColumnDescription column : columns) { for (int i = 0; i < fields.length; i++) {
list.add(column.getColumnClass()); fields[i] = columns[i].getColumnField();
} }
return list.toArray(new Class[columns.length]); return fields;
} }
} }
static String TABLE_NAME = "MatchTagTable"; static String TABLE_NAME = "MatchTagTable";
static Schema TABLE_SCHEMA = static Schema TABLE_SCHEMA = new Schema(0, "Key", ColumnDescription.getColumnFields(),
new Schema(0, LongField.class, "Key", ColumnDescription.getColumnClasses(), ColumnDescription.getColumnNames());
ColumnDescription.getColumnNames());
static VTMatchTagDBAdapter createAdapter(DBHandle dbHandle) throws IOException { static VTMatchTagDBAdapter createAdapter(DBHandle dbHandle) throws IOException {
return new VTMatchTagDBAdapterV0(dbHandle); return new VTMatchTagDBAdapterV0(dbHandle);
@@ -39,10 +39,10 @@ import ghidra.util.exception.*;
import ghidra.util.task.*; import ghidra.util.task.*;
public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTChangeManager { 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 String[] COL_TYPES = new String[] { "Value" };
private final static Schema SCHEMA = 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 PROGRAM_ID_PROPERTYLIST_NAME = "ProgramIDs";
private static final String SOURCE_PROGRAM_ID_PROPERTY_KEY = "SourceProgramID"; 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 long IMPLIED_MATCH_SET_ID = -1;
private static final String PROPERTY_TABLE_NAME = "PropertyTable"; private static final String PROPERTY_TABLE_NAME = "PropertyTable";
private static final String DB_VERSION_PROPERTY_NAME = "DB_VERSION"; 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 VTMatchSetTableDBAdapter matchSetTableAdapter;
private AssociationDatabaseManager associationManager; 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"); int ID = session.startTransaction("Constructing New Version Tracking Match Set");
try { try {
session.propertyTable = createPropertyTable(session.getDBHandle()); session.propertyTable = session.dbh.createTable(PROPERTY_TABLE_NAME, SCHEMA);
session.matchSetTableAdapter = session.matchSetTableAdapter = VTMatchSetTableDBAdapter.createAdapter(session.dbh);
VTMatchSetTableDBAdapter.createAdapter(session.getDBHandle());
session.associationManager = session.associationManager =
AssociationDatabaseManager.createAssociationManager(session.getDBHandle(), session); AssociationDatabaseManager.createAssociationManager(session.dbh, session);
session.matchTagAdapter = VTMatchTagDBAdapter.createAdapter(session.getDBHandle()); session.matchTagAdapter = VTMatchTagDBAdapter.createAdapter(session.dbh);
session.initializePrograms(sourceProgram, destinationProgram); session.initializePrograms(sourceProgram, destinationProgram);
session.createMatchSet( session.createMatchSet(
new ManualMatchProgramCorrelator(sourceProgram, destinationProgram), new ManualMatchProgramCorrelator(sourceProgram, destinationProgram),
@@ -91,6 +107,7 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTC
session.createMatchSet( session.createMatchSet(
new ImpliedMatchProgramCorrelator(sourceProgram, destinationProgram), new ImpliedMatchProgramCorrelator(sourceProgram, destinationProgram),
IMPLIED_MATCH_SET_ID); IMPLIED_MATCH_SET_ID);
session.updateVersion();
} }
finally { finally {
session.endTransaction(ID, true); session.endTransaction(ID, true);
@@ -105,21 +122,29 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTC
return session; return session;
} }
private static Table createPropertyTable(DBHandle dbh) throws IOException { private void updateVersion() throws IOException {
Table table = dbh.createTable(PROPERTY_TABLE_NAME, SCHEMA);
Record record = SCHEMA.createRecord(new StringField(DB_VERSION_PROPERTY_NAME)); Record record = SCHEMA.createRecord(new StringField(DB_VERSION_PROPERTY_NAME));
record.setString(0, Integer.toString(DB_VERSION)); record.setString(0, Integer.toString(DB_VERSION));
table.putRecord(record); propertyTable.putRecord(record);
return table;
} }
public static VTSessionDB getVTSession(DBHandle dbHandle, OpenMode openMode, Object consumer, public static VTSessionDB getVTSession(DBHandle dbHandle, OpenMode openMode, Object consumer,
TaskMonitor monitor) throws VersionException, IOException { TaskMonitor monitor) throws VersionException, IOException {
VTSessionDB session = new VTSessionDB(dbHandle, consumer); VTSessionDB session = new VTSessionDB(dbHandle, consumer);
if (session.getVersion() < DB_VERSION) { int storedVersion = session.getVersion();
throw new VersionException("Version Tracking Sessions do not support upgrades.");
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 = session.matchSetTableAdapter =
VTMatchSetTableDBAdapter.getAdapter(session.getDBHandle(), openMode, monitor); VTMatchSetTableDBAdapter.getAdapter(session.getDBHandle(), openMode, monitor);
session.associationManager = session.associationManager =
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,12 +15,11 @@
*/ */
package db; package db;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException; import java.io.IOException;
import db.buffers.DataBuffer; import db.buffers.DataBuffer;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/** /**
* <code>BTreeNode</code> defines a common interface for all types * <code>BTreeNode</code> defines a common interface for all types
@@ -30,17 +28,22 @@ import db.buffers.DataBuffer;
interface BTreeNode { 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(); public int getBufferId();
/** /**
* Return the data buffer associated with this node. * @return the data buffer associated with this node.
*/ */
public DataBuffer getBuffer(); public DataBuffer getBuffer();
/** /**
* Return the number of keys contained within this node. * @return the number of keys contained within this node.
*/ */
public int getKeyCount(); public int getKeyCount();
@@ -50,6 +53,26 @@ interface BTreeNode {
*/ */
public void setKeyCount(int cnt); 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. * Delete this node and all child nodes.
* @throws IOException thrown if IO error occurs * @throws IOException thrown if IO error occurs
@@ -67,11 +90,12 @@ interface BTreeNode {
* Check the consistency of this node and all of its children. * Check the consistency of this node and all of its children.
* @return true if consistency check passed, else false * @return true if consistency check passed, else false
* @param tableName name of table containing this node * @param tableName name of table containing this node
* @param monitor * @param monitor task monitor
* @throws IOException * @throws IOException if IO error occured
* @throws CancelledException if task cancelled
* @{@link ThrowsTag} CancelledException * @{@link ThrowsTag} CancelledException
*/ */
public boolean isConsistent(String tableName, TaskMonitor monitor) throws IOException, public boolean isConsistent(String tableName, TaskMonitor monitor)
CancelledException; throws IOException, CancelledException;
} }
@@ -15,11 +15,11 @@
*/ */
package db; package db;
import ghidra.util.exception.AssertException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.ArrayList; import java.util.ArrayList;
import ghidra.util.exception.AssertException;
/** /**
* Allows various non-database supported data types to be * Allows various non-database supported data types to be
* encoded within a BinaryField which may be stored within the * encoded within a BinaryField which may be stored within the
@@ -125,12 +125,12 @@ public class BinaryCodedField extends BinaryField {
public BinaryCodedField(byte[] values) { public BinaryCodedField(byte[] values) {
if (values != null) { if (values != null) {
data = new byte[values.length + 2]; data = new byte[values.length + 2];
data[DATA_OFFSET] = (byte)0; data[DATA_OFFSET] = (byte) 0;
System.arraycopy(values, 0, data, 2, values.length); System.arraycopy(values, 0, data, 2, values.length);
} }
else { else {
data = new byte[2]; data = new byte[2];
data[DATA_OFFSET] = (byte)-1; data[DATA_OFFSET] = (byte) -1;
} }
data[DATA_TYPE_OFFSET] = BYTE_ARRAY; data[DATA_TYPE_OFFSET] = BYTE_ARRAY;
} }
@@ -140,18 +140,18 @@ public class BinaryCodedField extends BinaryField {
* @param values short array * @param values short array
*/ */
public BinaryCodedField(short[] values) { 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); BinaryDataBuffer buffer = new BinaryDataBuffer(len);
buffer.putByte(DATA_TYPE_OFFSET, SHORT_ARRAY); buffer.putByte(DATA_TYPE_OFFSET, SHORT_ARRAY);
if (values != null) { if (values != null) {
int offset = DATA_OFFSET; int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0); buffer.putByte(offset++, (byte) 0);
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
offset = buffer.putShort(offset, values[i]); offset = buffer.putShort(offset, values[i]);
} }
} }
else { else {
buffer.putByte(DATA_OFFSET, (byte)-1); buffer.putByte(DATA_OFFSET, (byte) -1);
} }
data = buffer.getData(); data = buffer.getData();
} }
@@ -161,18 +161,18 @@ public class BinaryCodedField extends BinaryField {
* @param values int array * @param values int array
*/ */
public BinaryCodedField(int[] values) { 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); BinaryDataBuffer buffer = new BinaryDataBuffer(len);
buffer.putByte(DATA_TYPE_OFFSET, INT_ARRAY); buffer.putByte(DATA_TYPE_OFFSET, INT_ARRAY);
if (values != null) { if (values != null) {
int offset = DATA_OFFSET; int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0); buffer.putByte(offset++, (byte) 0);
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
offset = buffer.putInt(offset, values[i]); offset = buffer.putInt(offset, values[i]);
} }
} }
else { else {
buffer.putByte(DATA_OFFSET, (byte)-1); buffer.putByte(DATA_OFFSET, (byte) -1);
} }
data = buffer.getData(); data = buffer.getData();
} }
@@ -182,18 +182,18 @@ public class BinaryCodedField extends BinaryField {
* @param values long array * @param values long array
*/ */
public BinaryCodedField(long[] values) { 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); BinaryDataBuffer buffer = new BinaryDataBuffer(len);
buffer.putByte(DATA_TYPE_OFFSET, LONG_ARRAY); buffer.putByte(DATA_TYPE_OFFSET, LONG_ARRAY);
if (values != null) { if (values != null) {
int offset = DATA_OFFSET; int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0); buffer.putByte(offset++, (byte) 0);
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
offset = buffer.putLong(offset, values[i]); offset = buffer.putLong(offset, values[i]);
} }
} }
else { else {
buffer.putByte(DATA_OFFSET, (byte)-1); buffer.putByte(DATA_OFFSET, (byte) -1);
} }
data = buffer.getData(); data = buffer.getData();
} }
@@ -203,18 +203,18 @@ public class BinaryCodedField extends BinaryField {
* @param values float array * @param values float array
*/ */
public BinaryCodedField(float[] values) { 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); BinaryDataBuffer buffer = new BinaryDataBuffer(len);
buffer.putByte(DATA_TYPE_OFFSET, FLOAT_ARRAY); buffer.putByte(DATA_TYPE_OFFSET, FLOAT_ARRAY);
if (values != null) { if (values != null) {
int offset = DATA_OFFSET; int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0); buffer.putByte(offset++, (byte) 0);
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
offset = buffer.putInt(offset, Float.floatToIntBits(values[i])); offset = buffer.putInt(offset, Float.floatToIntBits(values[i]));
} }
} }
else { else {
buffer.putByte(DATA_OFFSET, (byte)-1); buffer.putByte(DATA_OFFSET, (byte) -1);
} }
data = buffer.getData(); data = buffer.getData();
} }
@@ -224,18 +224,18 @@ public class BinaryCodedField extends BinaryField {
* @param values double array * @param values double array
*/ */
public BinaryCodedField(double[] values) { 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); BinaryDataBuffer buffer = new BinaryDataBuffer(len);
buffer.putByte(DATA_TYPE_OFFSET, DOUBLE_ARRAY); buffer.putByte(DATA_TYPE_OFFSET, DOUBLE_ARRAY);
if (values != null) { if (values != null) {
int offset = DATA_OFFSET; int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0); buffer.putByte(offset++, (byte) 0);
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
offset = buffer.putLong(offset, Double.doubleToLongBits(values[i])); offset = buffer.putLong(offset, Double.doubleToLongBits(values[i]));
} }
} }
else { else {
buffer.putByte(DATA_OFFSET, (byte)-1); buffer.putByte(DATA_OFFSET, (byte) -1);
} }
data = buffer.getData(); data = buffer.getData();
} }
@@ -256,24 +256,26 @@ public class BinaryCodedField extends BinaryField {
} }
buffer = new BinaryDataBuffer(len); buffer = new BinaryDataBuffer(len);
int offset = DATA_OFFSET; int offset = DATA_OFFSET;
buffer.putByte(offset++, (byte)0); buffer.putByte(offset++, (byte) 0);
try { try {
for (int i = 0; i < strings.length; i++) { for (int i = 0; i < strings.length; i++) {
if (strings[i] == null) { if (strings[i] == null) {
offset = buffer.putInt(offset, -1); offset = buffer.putInt(offset, -1);
} else { }
else {
byte[] bytes = strings[i].getBytes(STRING_ENCODING); byte[] bytes = strings[i].getBytes(STRING_ENCODING);
offset = buffer.putInt(offset, bytes.length); offset = buffer.putInt(offset, bytes.length);
offset = buffer.put(offset, bytes); offset = buffer.put(offset, bytes);
} }
} }
} catch (UnsupportedEncodingException e) { }
catch (UnsupportedEncodingException e) {
throw new AssertException(); throw new AssertException();
} }
} }
else { else {
buffer = new BinaryDataBuffer(2); buffer = new BinaryDataBuffer(2);
buffer.putByte(DATA_OFFSET, (byte)-1); buffer.putByte(DATA_OFFSET, (byte) -1);
} }
buffer.putByte(DATA_TYPE_OFFSET, STRING_ARRAY); buffer.putByte(DATA_TYPE_OFFSET, STRING_ARRAY);
data = buffer.getData(); data = buffer.getData();
@@ -342,7 +344,7 @@ public class BinaryCodedField extends BinaryField {
if (data[DATA_OFFSET] < 0) { if (data[DATA_OFFSET] < 0) {
return null; return null;
} }
short[] values = new short[(data.length -2) / 2]; short[] values = new short[(data.length - 2) / 2];
BinaryDataBuffer buffer = new BinaryDataBuffer(data); BinaryDataBuffer buffer = new BinaryDataBuffer(data);
int offset = DATA_OFFSET + 1; int offset = DATA_OFFSET + 1;
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
@@ -364,7 +366,7 @@ public class BinaryCodedField extends BinaryField {
if (data[DATA_OFFSET] < 0) { if (data[DATA_OFFSET] < 0) {
return null; return null;
} }
int[] values = new int[(data.length -2) / 4]; int[] values = new int[(data.length - 2) / 4];
BinaryDataBuffer buffer = new BinaryDataBuffer(data); BinaryDataBuffer buffer = new BinaryDataBuffer(data);
int offset = DATA_OFFSET + 1; int offset = DATA_OFFSET + 1;
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
@@ -386,7 +388,7 @@ public class BinaryCodedField extends BinaryField {
if (data[DATA_OFFSET] < 0) { if (data[DATA_OFFSET] < 0) {
return null; return null;
} }
long[] values = new long[(data.length -2) / 8]; long[] values = new long[(data.length - 2) / 8];
BinaryDataBuffer buffer = new BinaryDataBuffer(data); BinaryDataBuffer buffer = new BinaryDataBuffer(data);
int offset = DATA_OFFSET + 1; int offset = DATA_OFFSET + 1;
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
@@ -408,7 +410,7 @@ public class BinaryCodedField extends BinaryField {
if (data[DATA_OFFSET] < 0) { if (data[DATA_OFFSET] < 0) {
return null; return null;
} }
float[] values = new float[(data.length -2) / 4]; float[] values = new float[(data.length - 2) / 4];
BinaryDataBuffer buffer = new BinaryDataBuffer(data); BinaryDataBuffer buffer = new BinaryDataBuffer(data);
int offset = DATA_OFFSET + 1; int offset = DATA_OFFSET + 1;
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
@@ -430,7 +432,7 @@ public class BinaryCodedField extends BinaryField {
if (data[DATA_OFFSET] < 0) { if (data[DATA_OFFSET] < 0) {
return null; return null;
} }
double[] values = new double[(data.length -2) / 8]; double[] values = new double[(data.length - 2) / 8];
BinaryDataBuffer buffer = new BinaryDataBuffer(data); BinaryDataBuffer buffer = new BinaryDataBuffer(data);
int offset = DATA_OFFSET + 1; int offset = DATA_OFFSET + 1;
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
@@ -463,11 +465,13 @@ public class BinaryCodedField extends BinaryField {
byte[] bytes = buffer.get(offset, len); byte[] bytes = buffer.get(offset, len);
strList.add(new String(bytes, STRING_ENCODING)); strList.add(new String(bytes, STRING_ENCODING));
offset += len; offset += len;
} else { }
else {
strList.add(null); strList.add(null);
} }
} }
} catch (UnsupportedEncodingException e) { }
catch (UnsupportedEncodingException e) {
throw new AssertException(); throw new AssertException();
} }
String[] strings = new String[strList.size()]; String[] strings = new String[strList.size()];
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import db.buffers.DataBuffer;
/** /**
* <code>BinaryField</code> provides a wrapper for variable length binary data which is read or * <code>BinaryField</code> provides a wrapper for variable length binary data which is read or
* written to a Record. * written to a Record.
*/ */
public class BinaryField extends Field { 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; protected byte[] data;
private Integer hashcode;
/** /**
* Construct a binary data field with an initial value of null. * Construct a binary data field with an initial value of null.
@@ -38,36 +45,41 @@ public class BinaryField extends Field {
* @param data initial value * @param data initial value
*/ */
public BinaryField(byte[] data) { 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; this.data = data;
} }
/* @Override
* @see ghidra.framework.store.db.Field#getBinaryData() void checkImmutable() {
*/ super.checkImmutable();
hashcode = null;
}
@Override @Override
public byte[] getBinaryData() { public byte[] getBinaryData() {
return data; return data;
} }
/*
* @see ghidra.framework.store.db.Field#setBinaryData(byte[])
*/
@Override @Override
public void setBinaryData(byte[] data) { public void setBinaryData(byte[] data) {
checkImmutable();
this.data = data; this.data = data;
} }
/*
* @see ghidra.framework.store.db.Field#length()
*/
@Override @Override
int length() { int length() {
return (data == null) ? 4 : (data.length + 4); return (data == null) ? 4 : (data.length + 4);
} }
/*
* @see ghidra.framework.store.db.Field#write(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int write(Buffer buf, int offset) throws IOException { int write(Buffer buf, int offset) throws IOException {
if (data == null) { if (data == null) {
@@ -77,11 +89,9 @@ public class BinaryField extends Field {
return buf.put(offset, data); return buf.put(offset, data);
} }
/*
* @see ghidra.framework.store.db.Field#read(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int read(Buffer buf, int offset) throws IOException { int read(Buffer buf, int offset) throws IOException {
checkImmutable();
int len = buf.getInt(offset); int len = buf.getInt(offset);
offset += 4; offset += 4;
if (len < 0) { if (len < 0) {
@@ -94,97 +104,25 @@ public class BinaryField extends Field {
return offset; return offset;
} }
/*
* @see ghidra.framework.store.db.Field#readLength(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int readLength(Buffer buf, int offset) throws IOException { int readLength(Buffer buf, int offset) throws IOException {
int len = buf.getInt(offset); int len = buf.getInt(offset);
return (len < 0 ? 0 : len) + 4; return (len < 0 ? 0 : len) + 4;
} }
/*
* @see ghidra.framework.store.db.Field#isVariableLength()
*/
@Override @Override
public boolean isVariableLength() { public boolean isVariableLength() {
return true; return true;
} }
/*
* @see ghidra.framework.store.db.Field#getFieldType()
*/
@Override @Override
protected byte getFieldType() { byte getFieldType() {
return BINARY_OBJ_TYPE; 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 @Override
void truncate(int length) { void truncate(int length) {
checkImmutable();
int maxLen = length - 4; int maxLen = length - 4;
if (data != null && data.length > maxLen) { if (data != null && data.length > maxLen) {
byte[] newData = new byte[maxLen]; byte[] newData = new byte[maxLen];
@@ -193,9 +131,6 @@ public class BinaryField extends Field {
} }
} }
/*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override @Override
public int compareTo(Field o) { public int compareTo(Field o) {
BinaryField f = (BinaryField) o; BinaryField f = (BinaryField) o;
@@ -224,28 +159,105 @@ public class BinaryField extends Field {
return len1 - len2; return len1 - len2;
} }
/*
* @see ghidra.framework.store.db.Field#newField(ghidra.framework.store.db.Field)
*/
@Override @Override
public Field newField(Field fieldValue) { int compareTo(DataBuffer buffer, int offset) {
return new BinaryField(fieldValue.getBinaryData()); 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 @Override
public Field newField() { public BinaryField copyField() {
return new BinaryField(getBinaryData().clone());
}
@Override
public BinaryField newField() {
return new BinaryField(); return new BinaryField();
} }
/* @Override
* @see java.lang.Object#hashCode() 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 @Override
public int hashCode() { 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 * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,15 +15,30 @@
*/ */
package db; package db;
import ghidra.util.exception.AssertException;
import java.io.IOException; import java.io.IOException;
import db.buffers.DataBuffer;
/** /**
* <code>BooleanField</code> provides a wrapper for boolean data which is read or * <code>BooleanField</code> provides a wrapper for boolean data which is read or
* written to a Record. * 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; private byte value;
@@ -39,70 +53,57 @@ public class BooleanField extends Field {
* @param b initial value * @param b initial value
*/ */
public BooleanField(boolean b) { 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; value = b ? (byte) 1 : (byte) 0;
} }
/*
* @see ghidra.framework.store.db.Field#getBooleanValue()
*/
@Override @Override
public boolean getBooleanValue() { public boolean getBooleanValue() {
return (value == 0) ? false : true; return (value == 0) ? false : true;
} }
/*
* @see ghidra.framework.store.db.Field#setBooleanValue(boolean)
*/
@Override @Override
public void setBooleanValue(boolean b) { public void setBooleanValue(boolean b) {
checkImmutable();
this.value = b ? (byte) 1 : (byte) 0; this.value = b ? (byte) 1 : (byte) 0;
} }
/*
* @see ghidra.framework.store.db.Field#length()
*/
@Override @Override
int length() { int length() {
return 1; return 1;
} }
/*
* @see ghidra.framework.store.db.Field#write(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int write(Buffer buf, int offset) throws IOException { int write(Buffer buf, int offset) throws IOException {
return buf.putByte(offset, value); return buf.putByte(offset, value);
} }
/*
* @see ghidra.framework.store.db.Field#read(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int read(Buffer buf, int offset) throws IOException { int read(Buffer buf, int offset) throws IOException {
checkImmutable();
value = buf.getByte(offset); value = buf.getByte(offset);
return offset + 1; return offset + 1;
} }
/*
* @see ghidra.framework.store.db.Field#readLength(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int readLength(Buffer buf, int offset) throws IOException { int readLength(Buffer buf, int offset) throws IOException {
return 1; return 1;
} }
/*
* @see ghidra.framework.store.db.Field#getFieldType()
*/
@Override @Override
protected byte getFieldType() { byte getFieldType() {
return BOOLEAN_TYPE; return BOOLEAN_TYPE;
} }
/*
* @see java.lang.Object#toString()
*/
@Override @Override
public String toString() { public String toString() {
return "BooleanField: " + Boolean.toString(getBooleanValue()); return "BooleanField: " + Boolean.toString(getBooleanValue());
@@ -113,9 +114,6 @@ public class BooleanField extends Field {
return Boolean.toString(getBooleanValue()); return Boolean.toString(getBooleanValue());
} }
/*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null || !(obj instanceof BooleanField)) if (obj == null || !(obj instanceof BooleanField))
@@ -124,9 +122,6 @@ public class BooleanField extends Field {
return otherField.value == value; return otherField.value == value;
} }
/*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override @Override
public int compareTo(Field o) { public int compareTo(Field o) {
BooleanField f = (BooleanField) o; BooleanField f = (BooleanField) o;
@@ -137,44 +132,58 @@ public class BooleanField extends Field {
return 1; return 1;
} }
/*
* @see ghidra.framework.store.db.Field#newField(ghidra.framework.store.db.Field)
*/
@Override @Override
public Field newField(Field fieldValue) { int compareTo(DataBuffer buffer, int offset) {
if (fieldValue.isVariableLength()) byte otherValue = buffer.getByte(offset);
throw new AssertException(); if (value == otherValue)
return new BooleanField(fieldValue.getLongValue() != 0); return 0;
else if (value < otherValue)
return -1;
return 1;
} }
/*
* @see ghidra.framework.store.db.Field#newField()
*/
@Override @Override
public Field newField() { public BooleanField copyField() {
return new BooleanField(getLongValue() != 0);
}
@Override
public BooleanField newField() {
return new BooleanField(); return new BooleanField();
} }
/*
* @see ghidra.framework.store.db.Field#getLongValue()
*/
@Override @Override
public long getLongValue() { public long getLongValue() {
return value; return value;
} }
/*
* @see ghidra.framework.store.db.Field#getBinaryData()
*/
@Override @Override
public byte[] getBinaryData() { public byte[] getBinaryData() {
return new byte[] { value }; return new byte[] { value };
} }
@Override
public void setBinaryData(byte[] bytes) {
checkImmutable();
if (bytes.length != 1) {
throw new IllegalFieldAccessException();
}
value = bytes[0];
}
@Override @Override
public int hashCode() { public int hashCode() {
// TODO Auto-generated method stub
return value; return value;
} }
@Override
BooleanField getMinValue() {
return MIN_VALUE;
}
@Override
BooleanField getMaxValue() {
return MAX_VALUE;
}
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -66,6 +65,7 @@ public interface Buffer {
/** /**
* Get the byte data located at the specified offset. * Get the byte data located at the specified offset.
* @param offset byte offset from start of buffer. * @param offset byte offset from start of buffer.
* @param length number of bytes to be read and returned
* @return the byte array. * @return the byte array.
* @throws ArrayIndexOutOfBoundsException is thrown if an invalid offset is * @throws ArrayIndexOutOfBoundsException is thrown if an invalid offset is
* specified or the end of the buffer was encountered while reading the * specified or the end of the buffer was encountered while reading the
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,15 +15,30 @@
*/ */
package db; package db;
import ghidra.util.exception.AssertException;
import java.io.IOException; import java.io.IOException;
import db.buffers.DataBuffer;
/** /**
* <code>ByteField</code> provides a wrapper for single signed byte data * <code>ByteField</code> provides a wrapper for single signed byte data
* which is read or written to a Record. * 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; private byte value;
@@ -39,69 +53,57 @@ public class ByteField extends Field {
* @param b initial value * @param b initial value
*/ */
public ByteField(byte b) { 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; value = b;
} }
/*
* @see ghidra.framework.store.db.Field#getByteValue()
*/
@Override @Override
public byte getByteValue() { public byte getByteValue() {
return value; return value;
} }
/*
* @see ghidra.framework.store.db.Field#setByteValue(byte)
*/
@Override @Override
public void setByteValue(byte value) { public void setByteValue(byte value) {
checkImmutable();
this.value = value; this.value = value;
} }
/*
* @see ghidra.framework.store.db.Field#length()
*/
@Override @Override
int length() { int length() {
return 1; return 1;
} }
/*
* @see ghidra.framework.store.db.Field#write(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int write(Buffer buf, int offset) throws IOException { int write(Buffer buf, int offset) throws IOException {
return buf.putByte(offset, value); return buf.putByte(offset, value);
} }
/*
* @see ghidra.framework.store.db.Field#read(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int read(Buffer buf, int offset) throws IOException { int read(Buffer buf, int offset) throws IOException {
checkImmutable();
value = buf.getByte(offset); value = buf.getByte(offset);
return offset + 1; return offset + 1;
} }
/*
* @see ghidra.framework.store.db.Field#readLength(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int readLength(Buffer buf, int offset) throws IOException { int readLength(Buffer buf, int offset) throws IOException {
return 1; return 1;
} }
/*
* @see ghidra.framework.store.db.Field#getFieldType()
*/
@Override @Override
protected byte getFieldType() { byte getFieldType() {
return BYTE_TYPE; return BYTE_TYPE;
} }
/*
* @see java.lang.Object#toString()
*/
@Override @Override
public String toString() { public String toString() {
return "Byte: " + Byte.toString(value); return "Byte: " + Byte.toString(value);
@@ -109,12 +111,9 @@ public class ByteField extends Field {
@Override @Override
public String getValueAsString() { public String getValueAsString() {
return Integer.toHexString(value); return "0x" + Integer.toHexString(value);
} }
/*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null || !(obj instanceof ByteField)) if (obj == null || !(obj instanceof ByteField))
@@ -122,9 +121,6 @@ public class ByteField extends Field {
return ((ByteField) obj).value == value; return ((ByteField) obj).value == value;
} }
/*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override @Override
public int compareTo(Field o) { public int compareTo(Field o) {
ByteField f = (ByteField) o; ByteField f = (ByteField) o;
@@ -135,54 +131,63 @@ public class ByteField extends Field {
return 1; return 1;
} }
/*
* @see ghidra.framework.store.db.Field#newField(ghidra.framework.store.db.Field)
*/
@Override @Override
public Field newField(Field fieldValue) { int compareTo(DataBuffer buffer, int offset) {
if (fieldValue.isVariableLength()) byte otherValue = buffer.getByte(offset);
throw new AssertException(); if (value == otherValue)
return new ByteField((byte) fieldValue.getLongValue()); return 0;
else if (value < otherValue)
return -1;
return 1;
} }
/*
* @see ghidra.framework.store.db.Field#newField()
*/
@Override @Override
public Field newField() { public ByteField copyField() {
return new ByteField((byte) getLongValue());
}
@Override
public ByteField newField() {
return new ByteField(); return new ByteField();
} }
/*
* @see ghidra.framework.store.db.Field#getLongValue()
*/
@Override @Override
public long getLongValue() { public long getLongValue() {
return value; return value;
} }
/*
* @see ghidra.framework.store.db.Field#setLongValue(long)
*/
@Override @Override
public void setLongValue(long value) { public void setLongValue(long value) {
this.value = (byte) value; setByteValue((byte) value);
} }
/*
* @see ghidra.framework.store.db.Field#getBinaryData()
*/
@Override @Override
public byte[] getBinaryData() { public byte[] getBinaryData() {
return new byte[] { value }; return new byte[] { value };
} }
/* @Override
* @see java.lang.Object#hashCode() public void setBinaryData(byte[] bytes) {
*/ checkImmutable();
if (bytes.length != 1) {
throw new IllegalFieldAccessException();
}
value = bytes[0];
}
@Override @Override
public int hashCode() { public int hashCode() {
return value; 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 * @param unintializedDataSourceOffset uninitialized data source offset which corresponds to
* this buffers contents. * this buffers contents.
* @param bufferMgr database buffer manager * @param bufferMgr database buffer manager
* @throws IOException * @throws IOException thrown if an IO error occurs
*/ */
public ChainedBuffer(int size, boolean enableObfuscation, Buffer uninitializedDataSource, public ChainedBuffer(int size, boolean enableObfuscation, Buffer uninitializedDataSource,
int unintializedDataSourceOffset, BufferMgr bufferMgr) throws IOException { int unintializedDataSourceOffset, BufferMgr bufferMgr) throws IOException {
@@ -171,7 +171,7 @@ public class ChainedBuffer implements Buffer {
* @param size {@literal buffer size (0 < size <= 0x7fffffff)} * @param size {@literal buffer size (0 < size <= 0x7fffffff)}
* @param enableObfuscation true to enable xor-ing of stored data to facilitate data obfuscation. * @param enableObfuscation true to enable xor-ing of stored data to facilitate data obfuscation.
* @param bufferMgr database buffer manager * @param bufferMgr database buffer manager
* @throws IOException * @throws IOException thrown if an IO error occurs
*/ */
public ChainedBuffer(int size, boolean enableObfuscation, BufferMgr bufferMgr) public ChainedBuffer(int size, boolean enableObfuscation, BufferMgr bufferMgr)
throws IOException { throws IOException {
@@ -183,7 +183,7 @@ public class ChainedBuffer implements Buffer {
* This method may only be invoked while a database transaction is in progress. * This method may only be invoked while a database transaction is in progress.
* @param size {@literal buffer size (0 < size <= 0x7fffffff)} * @param size {@literal buffer size (0 < size <= 0x7fffffff)}
* @param bufferMgr database buffer manager * @param bufferMgr database buffer manager
* @throws IOException * @throws IOException thrown if an IO error occurs
*/ */
public ChainedBuffer(int size, BufferMgr bufferMgr) throws IOException { public ChainedBuffer(int size, BufferMgr bufferMgr) throws IOException {
this(size, false, null, 0, bufferMgr); 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. * This should not be specified if buffer will be completely filled/initialized.
* @param unintializedDataSourceOffset uninitialized data source offset which corresponds to * @param unintializedDataSourceOffset uninitialized data source offset which corresponds to
* this buffers contents. * this buffers contents.
* @throws IOException * @throws IOException thrown if an IO error occurs
*/ */
public ChainedBuffer(BufferMgr bufferMgr, int bufferId, Buffer uninitializedDataSource, public ChainedBuffer(BufferMgr bufferMgr, int bufferId, Buffer uninitializedDataSource,
int unintializedDataSourceOffset) throws IOException { int unintializedDataSourceOffset) throws IOException {
@@ -238,6 +238,7 @@ public class ChainedBuffer implements Buffer {
* Construct an existing chained buffer. * Construct an existing chained buffer.
* @param bufferMgr database buffer manager * @param bufferMgr database buffer manager
* @param bufferId database buffer ID which corresponds to a stored ChainedBuffer * @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 { public ChainedBuffer(BufferMgr bufferMgr, int bufferId) throws IOException {
this(bufferMgr, bufferId, null, 0); 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. * specified bufferOffset.
* @param bufferOffset offset within a single chained buffer, valid values are in the * @param bufferOffset offset within a single chained buffer, valid values are in the
* range 0 to (dataSpace-1). * range 0 to (dataSpace-1). This value is used to determine the appropriate XOR mask.
* @param byteValue * @param byteValue value to be XOR'd against appropriate mask value
* @return * @return XOR'd value
*/ */
private byte xorMaskByte(int bufferOffset, byte byteValue) { private byte xorMaskByte(int bufferOffset, byte byteValue) {
byte maskByte = XOR_MASK_BYTES[bufferOffset % XOR_MASK_BYTES.length]; 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 * @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. * range 0 to (dataSpace-1). The value (bufferOffset+len-1) must be less than dataSpace.
* @param len mask length (2, 4, or 8) * @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) { private long getXorMask(int bufferOffset, int len) {
long mask = 0; 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 * The same uninitialized read-only dataSource used for a chained buffer should be re-applied
* anytime this chained buffer is re-instantiated. * anytime this chained buffer is re-instantiated.
* *
* @param dataSource * @param dataSource data source for unitilized bytes
* @param dataSourceOffset * @param dataSourceOffset offset within dataSource which corresponds to first byte of
* this chained buffer.
*/ */
private void setUnintializedDataSource(Buffer dataSource, int dataSourceOffset) { 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. * 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. * 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() { int getBufferCount() {
return dataBufferIdTable.length + return dataBufferIdTable.length +
@@ -734,7 +737,7 @@ public class ChainedBuffer implements Buffer {
* The index buffer provided is always released. * The index buffer provided is always released.
* @param indexBuffer the last index buffer. * @param indexBuffer the last index buffer.
* @return DataBuffer * @return DataBuffer
* @throws IOException * @throws IOException thrown if an IO error occurs
*/ */
private DataBuffer appendIndexBuffer(DataBuffer indexBuffer) throws IOException { private DataBuffer appendIndexBuffer(DataBuffer indexBuffer) throws IOException {
try { try {
@@ -856,6 +859,7 @@ public class ChainedBuffer implements Buffer {
/** /**
* Delete and release all underlying DataBuffers. * Delete and release all underlying DataBuffers.
* @throws IOException thrown if an IO error occurs
*/ */
public synchronized void delete() throws IOException { public synchronized void delete() throws IOException {
if (readOnly) { if (readOnly) {
@@ -1115,6 +1119,7 @@ public class ChainedBuffer implements Buffer {
* @param startOffset starting offset, inclusive * @param startOffset starting offset, inclusive
* @param endOffset ending offset, exclusive * @param endOffset ending offset, exclusive
* @param fillByte byte value * @param fillByte byte value
* @throws IOException thrown if an IO error occurs
*/ */
public synchronized void fill(int startOffset, int endOffset, byte fillByte) public synchronized void fill(int startOffset, int endOffset, byte fillByte)
throws IOException { throws IOException {
@@ -1160,7 +1165,7 @@ public class ChainedBuffer implements Buffer {
* @return int actual number of bytes written. * @return int actual number of bytes written.
* This could be smaller than length if the end of buffer is * This could be smaller than length if the end of buffer is
* encountered while writing data. * 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) private int putBytes(int index, int bufferDataOffset, byte[] data, int dataOffset, int length)
throws IOException { throws IOException {
@@ -1370,9 +1375,6 @@ public class ChainedBuffer implements Buffer {
return offset + 8; return offset + 8;
} }
/*
* @see ghidra.framework.store.Buffer#putShort(int, short)
*/
@Override @Override
public synchronized int putShort(int offset, short v) throws IOException { public synchronized int putShort(int offset, short v) throws IOException {
if (readOnly) { if (readOnly) {
@@ -1406,7 +1408,7 @@ public class ChainedBuffer implements Buffer {
* Get a data buffer. * Get a data buffer.
* @param index index of within buffer chain * @param index index of within buffer chain
* @return requested data buffer. * @return requested data buffer.
* @throws IOException * @throws IOException thrown if an IO error occurs
*/ */
private DataBuffer getBuffer(int index) throws IOException { private DataBuffer getBuffer(int index) throws IOException {
// if databufferIdTable is null, index must be null. let it throw null pointer in this case. // 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. * Initialize specified DataBuffer which corresponds to the chain index.
* @param chainBufferIndex chain buffer index * @param chainBufferIndex chain buffer index
* @param buf newly allocated database buffer * @param buf newly allocated database buffer
* @throws IOException * @throws IOException thrown if an IO error occurs
*/ */
private void initializeAllocatedBuffer(int chainBufferIndex, DataBuffer buf) private void initializeAllocatedBuffer(int chainBufferIndex, DataBuffer buf)
throws IOException { throws IOException {
@@ -1455,7 +1457,7 @@ public class ChainedBuffer implements Buffer {
* Add a new data buffer as an indexed buffer. * Add a new data buffer as an indexed buffer.
* @param index buffer index. * @param index buffer index.
* @param buf new data buffer. * @param buf new data buffer.
* @throws IOException * @throws IOException thrown if an IO error occurs
*/ */
private void addBuffer(int index, DataBuffer buf) throws IOException { private void addBuffer(int index, DataBuffer buf) throws IOException {
buf.putByte(NODE_TYPE_OFFSET, NodeMgr.CHAINED_BUFFER_DATA_NODE); 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 java.io.IOException;
import db.buffers.DataBuffer;
/** /**
* <code>Field</code> is an abstract data wrapper for use with Records. * <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 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 * Field type for ByteField
* @see db.ByteField * @see db.ByteField
@@ -65,19 +78,56 @@ public abstract class Field implements Comparable<Field> {
static final byte BOOLEAN_TYPE = 6; 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. * Get field as a long value.
@@ -191,10 +241,11 @@ public abstract class Field implements Comparable<Field> {
* Set data from binary byte array. * Set data from binary byte array.
* All variable-length fields must implement this method. * All variable-length fields must implement this method.
* @param bytes field data * @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) { abstract public void setBinaryData(byte[] bytes);
throw new IllegalFieldAccessException();
}
/** /**
* Get field as a String value. * 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. * Truncate a variable length field to the specified length.
* If current length is shorterm, this method has no affect. * If current length is shorterm, this method has no affect.
* @param length * @param length truncated length
*/ */
void truncate(int 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. * Determine if specified field is same type as this field
* @param fieldValue initial field value. * @param field a Field instance
* @return long * @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. * Create new instance of this field type.
* @return long * @return new field instance with undefined initial value
*/ */
public abstract Field newField(); 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 * 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(); abstract int length();
/*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override @Override
public abstract boolean equals(Object obj); public abstract boolean equals(Object obj);
@Override @Override
public abstract int hashCode(); public abstract int hashCode();
/**
* Get field value as a formatted string
* @return field value string
*/
public abstract String getValueAsString(); 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. * Get the field associated with the specified type value.
* @param fieldType encoded Field type * @param fieldType field type index
* @return Field * @return Field field instance which corresponds to the specified fieldType
* @throws UnsupportedFieldException if unsupported fieldType specified * @throws UnsupportedFieldException if unsupported fieldType specified
*/ */
static Field getField(byte fieldType) throws UnsupportedFieldException { 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: case LONG_TYPE:
return new LongField(); return LongField.INSTANCE;
case INT_TYPE: case INT_TYPE:
return new IntField(); return IntField.INSTANCE;
case STRING_TYPE: case STRING_TYPE:
return new StringField(); return StringField.INSTANCE;
case SHORT_TYPE: case SHORT_TYPE:
return new ShortField(); return ShortField.INSTANCE;
case BYTE_TYPE: case BYTE_TYPE:
return new ByteField(); return ByteField.INSTANCE;
case BOOLEAN_TYPE: case BOOLEAN_TYPE:
return new BooleanField(); return BooleanField.INSTANCE;
case BINARY_OBJ_TYPE: case BINARY_OBJ_TYPE:
return new BinaryField(); return BinaryField.INSTANCE;
case FIXED_10_TYPE:
return FixedField10.INSTANCE;
} }
} }
else { 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 * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,11 +15,10 @@
*/ */
package db; package db;
import ghidra.util.exception.AssertException;
import java.io.IOException; import java.io.IOException;
import db.buffers.DataBuffer; import db.buffers.DataBuffer;
import ghidra.util.exception.AssertException;
/** /**
* <code>FixedRecNode</code> is an implementation of a BTree leaf node * <code>FixedRecNode</code> is an implementation of a BTree leaf node
@@ -51,6 +49,7 @@ class FixedRecNode extends LongKeyRecordNode {
* Construct an existing long-key fixed-length record leaf node. * Construct an existing long-key fixed-length record leaf node.
* @param nodeMgr table node manager instance * @param nodeMgr table node manager instance
* @param buf node buffer * @param buf node buffer
* @param recordLength fixed record length
*/ */
FixedRecNode(NodeMgr nodeMgr, DataBuffer buf, int recordLength) { FixedRecNode(NodeMgr nodeMgr, DataBuffer buf, int recordLength) {
super(nodeMgr, buf); super(nodeMgr, buf);
@@ -64,45 +63,37 @@ class FixedRecNode extends LongKeyRecordNode {
* @param recordLength fixed record length * @param recordLength fixed record length
* @param prevLeafId node buffer id for previous leaf ( &lt; 0: no leaf) * @param prevLeafId node buffer id for previous leaf ( &lt; 0: no leaf)
* @param nextLeafId node buffer id for next 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); super(nodeMgr, NodeMgr.LONGKEY_FIXED_REC_NODE, prevLeafId, nextLeafId);
this.recordLength = recordLength; this.recordLength = recordLength;
entrySize = KEY_SIZE + recordLength; entrySize = KEY_SIZE + recordLength;
} }
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#createNewLeaf(int, int)
*/
@Override @Override
LongKeyRecordNode createNewLeaf(int prevLeafId, int nextLeafId) throws IOException { LongKeyRecordNode createNewLeaf(int prevLeafId, int nextLeafId) throws IOException {
return new FixedRecNode(nodeMgr, recordLength, prevLeafId, nextLeafId); return new FixedRecNode(nodeMgr, recordLength, prevLeafId, nextLeafId);
} }
/*
* @see ghidra.framework.store.db.LongKeyNode#getKey(int)
*/
@Override @Override
long getKey(int index) { long getKey(int index) {
return buffer.getLong(ENTRY_BASE_OFFSET + (index * entrySize)); return buffer.getLong(getKeyOffset(index));
} }
// /** @Override
// * Store a key at the specified index public int getKeyOffset(int index) {
// * @param index key index return ENTRY_BASE_OFFSET + (index * entrySize);
// * @param key key value }
// */
// private void putKey(int index, long key) {
// buffer.putLong(ENTRY_BASE_OFFSET + (index * entrySize), key);
// }
/** /**
* Get the record offset within the buffer * Get the record offset within the buffer
* @param index key index * @param index key index
* @return record offset * @return record offset
*/ */
private int getRecordOffset(int index) { @Override
public int getRecordOffset(int index) {
return ENTRY_BASE_OFFSET + (index * entrySize); return ENTRY_BASE_OFFSET + (index * entrySize);
} }
@@ -128,30 +119,22 @@ class FixedRecNode extends LongKeyRecordNode {
buffer.move(start, offset, len); buffer.move(start, offset, len);
} }
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#remove(int)
*/
@Override @Override
void remove(int index) { public void remove(int index) {
if (index < 0 || index >= keyCount) if (index < 0 || index >= keyCount)
throw new AssertException(); throw new AssertException();
shiftRecords(index + 1, false); shiftRecords(index + 1, false);
setKeyCount(keyCount - 1); setKeyCount(keyCount - 1);
} }
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#insertRecord(int, ghidra.framework.store.db.Record)
*/
@Override @Override
boolean insertRecord(int index, Record record) throws IOException { boolean insertRecord(int index, Record record) throws IOException {
// Check for use of indirect chained record node(s) if (keyCount == ((buffer.length() - HEADER_SIZE) / entrySize)) {
// int len = record.length();
if (keyCount == ((buffer.length() - HEADER_SIZE) / entrySize))
return false; // insufficient space for record storage return false; // insufficient space for record storage
}
// Make room for new record // Make room for new record
shiftRecords(index, true); shiftRecords(index, true);
@@ -165,21 +148,15 @@ throw new AssertException();
return true; return true;
} }
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#updateRecord(int, ghidra.framework.store.db.Record)
*/
@Override @Override
LongKeyNode updateRecord(int index, Record record) throws IOException { LongKeyNode updateRecord(int index, Record record) throws IOException {
int offset = getRecordOffset(index) + KEY_SIZE; int offset = getRecordOffset(index) + KEY_SIZE;
record.write(buffer, offset); record.write(buffer, offset);
return getRoot(); return getRoot();
} }
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#getRecord(long, ghidra.framework.store.db.Schema)
*/
@Override @Override
Record getRecord(long key, Schema schema) throws IOException { Record getRecord(long key, Schema schema) throws IOException {
int index = getKeyIndex(key); int index = getKeyIndex(key);
if (index < 0) if (index < 0)
return null; return null;
@@ -188,22 +165,16 @@ throw new AssertException();
return record; return record;
} }
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#getRecord(ghidra.framework.store.db.Schema, int)
*/
@Override @Override
Record getRecord(Schema schema, int index) throws IOException { public Record getRecord(Schema schema, int index) throws IOException {
long key = getKey(index); long key = getKey(index);
Record record = schema.createRecord(key); Record record = schema.createRecord(key);
record.read(buffer, getRecordOffset(index) + KEY_SIZE); record.read(buffer, getRecordOffset(index) + KEY_SIZE);
return record; return record;
} }
/*
* @see ghidra.framework.store.db.LongKeyRecordNode#splitData(ghidra.framework.store.db.LongKeyRecordNode)
*/
@Override @Override
void splitData(LongKeyRecordNode newRightLeaf) { void splitData(LongKeyRecordNode newRightLeaf) {
FixedRecNode rightNode = (FixedRecNode) newRightLeaf; FixedRecNode rightNode = (FixedRecNode) newRightLeaf;
@@ -221,17 +192,12 @@ throw new AssertException();
rightNode.setKeyCount(count); rightNode.setKeyCount(count);
} }
/*
* @see ghidra.framework.store.db.LongKeyNode#delete()
*/
@Override @Override
public void delete() throws IOException { public void delete() throws IOException {
nodeMgr.deleteNode(this); nodeMgr.deleteNode(this);
} }
/* @Override
* @see ghidra.framework.store.db.BTreeNode#getBufferReferences()
*/
public int[] getBufferReferences() { public int[] getBufferReferences() {
return EMPTY_ID_LIST; return EMPTY_ID_LIST;
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -27,4 +26,13 @@ public class IllegalFieldAccessException extends RuntimeException {
IllegalFieldAccessException() { IllegalFieldAccessException() {
super("Illegal field access"); 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 * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,15 +15,30 @@
*/ */
package db; package db;
import ghidra.util.exception.AssertException;
import java.io.IOException; import java.io.IOException;
import db.buffers.DataBuffer;
/** /**
* <code>IntField</code> provides a wrapper for 4-byte signed integer data * <code>IntField</code> provides a wrapper for 4-byte signed integer data
* which is read or written to a Record. * 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; private int value;
@@ -39,69 +53,57 @@ public class IntField extends Field {
* @param i initial value * @param i initial value
*/ */
public IntField(int i) { 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 @Override
public int getIntValue() { public int getIntValue() {
return value; return value;
} }
/**
* @see db.Field#setIntValue(int)
*/
@Override @Override
public void setIntValue(int value) { public void setIntValue(int value) {
checkImmutable();
this.value = value; this.value = value;
} }
/**
* @see db.Field#length()
*/
@Override @Override
int length() { int length() {
return 4; return 4;
} }
/**
* @see db.Field#write(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int write(Buffer buf, int offset) throws IOException { int write(Buffer buf, int offset) throws IOException {
return buf.putInt(offset, value); return buf.putInt(offset, value);
} }
/**
* @see db.Field#read(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int read(Buffer buf, int offset) throws IOException { int read(Buffer buf, int offset) throws IOException {
checkImmutable();
value = buf.getInt(offset); value = buf.getInt(offset);
return offset + 4; return offset + 4;
} }
/**
* @see db.Field#readLength(ghidra.framework.store.Buffer, int)
*/
@Override @Override
int readLength(Buffer buf, int offset) throws IOException { int readLength(Buffer buf, int offset) throws IOException {
return 4; return 4;
} }
/**
* @see db.Field#getFieldType()
*/
@Override @Override
protected byte getFieldType() { byte getFieldType() {
return INT_TYPE; return INT_TYPE;
} }
/**
* @see java.lang.Object#toString()
*/
@Override @Override
public String toString() { public String toString() {
return "IntField: " + Integer.toString(value); return "IntField: " + Integer.toString(value);
@@ -109,12 +111,9 @@ public class IntField extends Field {
@Override @Override
public String getValueAsString() { public String getValueAsString() {
return Integer.toHexString(value); return "0x" + Integer.toHexString(value);
} }
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null || !(obj instanceof IntField)) if (obj == null || !(obj instanceof IntField))
@@ -122,9 +121,6 @@ public class IntField extends Field {
return ((IntField) obj).value == value; return ((IntField) obj).value == value;
} }
/**
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override @Override
public int compareTo(Field o) { public int compareTo(Field o) {
IntField f = (IntField) o; IntField f = (IntField) o;
@@ -135,54 +131,64 @@ public class IntField extends Field {
return 1; return 1;
} }
/**
* @see db.Field#newField(docking.widgets.fieldpanel.Field)
*/
@Override @Override
public Field newField(Field fieldValue) { int compareTo(DataBuffer buffer, int offset) {
if (fieldValue.isVariableLength()) int otherValue = buffer.getInt(offset);
throw new AssertException(); if (value == otherValue)
return new IntField((int) fieldValue.getLongValue()); return 0;
else if (value < otherValue)
return -1;
return 1;
} }
/**
* @see db.Field#newField()
*/
@Override @Override
public Field newField() { public IntField copyField() {
return new IntField((int) getLongValue());
}
@Override
public IntField newField() {
return new IntField(); return new IntField();
} }
/**
* @see db.Field#getLongValue()
*/
@Override @Override
public long getLongValue() { public long getLongValue() {
return value; return value;
} }
/**
* @see db.Field#setLongValue(long)
*/
@Override @Override
public void setLongValue(long value) { public void setLongValue(long value) {
this.value = (int) value; setIntValue((int) value);
} }
/**
* @see db.Field#getBinaryData()
*/
@Override @Override
public byte[] getBinaryData() { public byte[] getBinaryData() {
return new byte[] { (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), return new byte[] { (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8),
(byte) value }; (byte) value };
} }
/** @Override
* @see java.lang.Object#hashCode() 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 @Override
public int hashCode() { public int hashCode() {
return value; return value;
} }
@Override
IntField getMinValue() {
return MIN_VALUE;
}
@Override
IntField getMaxValue() {
return MAX_VALUE;
}
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.feature.vt.api.db; package db;
public class VTAppliedMarkupTableDBAdapterV0 {
/**
* 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