Skip to content

Commit 4b0422a

Browse files
committed
use a MappedByteBuffer to access the file, refactor the different BDB
page types into separate classes
1 parent c1681b8 commit 4b0422a

File tree

1 file changed

+131
-104
lines changed

1 file changed

+131
-104
lines changed

src/main/java/prof7bit/bitcoin/wallettool/fileformats/WalletDatHandler.xtend

Lines changed: 131 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import java.io.UnsupportedEncodingException
99
import java.math.BigInteger
1010
import java.nio.ByteBuffer
1111
import java.nio.ByteOrder
12+
import java.nio.channels.FileChannel
1213
import java.util.ArrayList
1314
import java.util.Arrays
1415
import java.util.HashMap
@@ -29,20 +30,6 @@ import prof7bit.bitcoin.wallettool.exceptions.FormatFoundNeedPasswordException
2930
class WalletDatHandler extends AbstractImportExportHandler {
3031
val log = LoggerFactory.getLogger(WalletDatHandler)
3132

32-
// these are the only ones we support
33-
static val MAGIC = 0x53162
34-
static val VERSION = 9
35-
36-
// page types
37-
static val P_LBTREE = 5 /* Btree leaf. */
38-
39-
var RandomAccessFile f
40-
val bb2 = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN)
41-
val bb4 = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN)
42-
43-
var long pagesize
44-
var int last_pgno
45-
4633
/**
4734
* this list will contain all items from the bdb file,
4835
* its alternating key and value data. It has an even
@@ -73,23 +60,17 @@ class WalletDatHandler extends AbstractImportExportHandler {
7360
* db_page.h, db_dump and a hex editor.
7461
*/
7562
override load(File file, String password, String password2) throws Exception {
63+
var RandomAccessFile raf
7664
try {
7765
log.info("opening file {}", file)
78-
f = new RandomAccessFile(file, "r")
79-
val magic = readMagic
80-
val version = readVersion
81-
if (magic != MAGIC || version != VERSION) {
82-
throw new Exception("this is not a valid wallet.dat file")
83-
}
84-
pagesize = readPageSize
85-
last_pgno = readLastPgno
66+
raf = new RandomAccessFile(file, "r")
8667

87-
parseBerkeleyFile
68+
parseBerkeleyFile(raf)
8869
parseBitcoinData
8970
decryptAndImport(password)
9071

9172
} finally {
92-
f.close
73+
raf.close
9374
}
9475
}
9576

@@ -341,6 +322,7 @@ class WalletDatHandler extends AbstractImportExportHandler {
341322
// ************* reading Berkeley-specific structures from the file
342323
//
343324

325+
344326
/**
345327
* Parse the wallet.dat file, find all b-tree leaf
346328
* pages in the file and put all their items into
@@ -349,12 +331,26 @@ class WalletDatHandler extends AbstractImportExportHandler {
349331
* no next page. When this function has returned we
350332
* have all key/value items in the bdbKeyValueItems list.
351333
*/
352-
private def parseBerkeleyFile() throws IOException {
334+
private def parseBerkeleyFile(RandomAccessFile raf) throws Exception {
335+
// these are the only ones we support
336+
val MAGIC = 0x53162
337+
val VERSION = 9
338+
339+
val head = new BerkeleyDBHeaderPage(raf)
340+
val magic = head.magic
341+
val version = head.version
342+
if (magic != MAGIC || version != VERSION) {
343+
throw new Exception("this is not a valid wallet.dat file")
344+
}
345+
val pagesize = head.pageSize
346+
val last_pgno = head.lastPgno
347+
353348
bdbKeyValueItems.clear
354-
for (p : 0..last_pgno) {
349+
for (pgno : 0..last_pgno) {
355350
// find a root leaf
356-
if (p.readPageType == P_LBTREE && p.readPrevPgno == 0){
357-
readAllLeafPages(p)
351+
val page = new BerkeleyDBLeafPage(raf, pgno, pagesize)
352+
if (page.isLBTREE && page.isRootPage){
353+
readAllLeafPages(page)
358354
}
359355
}
360356
log.debug("parsing done, found {} key/value pairs in db file", bdbKeyValueItems.length / 2)
@@ -366,129 +362,160 @@ class WalletDatHandler extends AbstractImportExportHandler {
366362
* items into the bdbKeyValueItems list until
367363
* there is no next page anymore.
368364
*/
369-
private def readAllLeafPages(int p_start) throws IOException {
370-
var p = p_start
371-
while (p != 0){
372-
readLeafPage(p)
373-
p = p.readNextPgno
365+
private def readAllLeafPages(BerkeleyDBLeafPage root) throws IOException {
366+
var page = root
367+
while (page.hasNextPage){
368+
readLeafPage(page)
369+
page = page.nextLeafPage
374370
}
375371
}
376372

377373
/**
378374
* parse this leaf page and add all
379375
* its items to the bdbKeyValueItems list
380376
*/
381-
private def readLeafPage(int p) throws IOException {
382-
val count_entries = p.readEntryCount
383-
log.debug("page {} contains {} entries", p, count_entries)
384-
for (i : 0..<count_entries){
385-
val o = p.getDataOffset(i)
386-
val size = readShortAt(o).bitwiseAnd(0xffff)
387-
val type = readByteAt(o + 2)
388-
if (type == 1) {
389-
val data = readByteArrayAt(o + 3, size)
390-
bdbKeyValueItems.add(ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN))
377+
private def readLeafPage(BerkeleyDBLeafPage page) {
378+
val count = page.entryCount
379+
log.debug("page {} contains {} entries", page.pgno , count)
380+
for (i : 0..<count){
381+
if (page.getItemType(i) == 1) {
382+
val data = page.getItemData(i)
383+
bdbKeyValueItems.add(data)
391384
}
392385
}
393386
}
394387

395-
static val START_TABLE = 26
388+
//
389+
// ************* saving (won't be implemented)
390+
//
396391

397-
/**
398-
* Right after the header of a b-tree leaf page there is a
399-
* table with 16bit words, each data item has an entry in this
400-
* table. They are the offsets of the actual data (relative to
401-
* the start of the page). Given a page number of a b-tree
402-
* page and an item index this function will look up the table
403-
* and return the absolute file offset of this item.
404-
*/
405-
private def long getDataOffset(int p, int index) throws IOException {
406-
val lookup_table_offs = p * pagesize + START_TABLE + 2 * index
407-
val item_offs = readShortAt(lookup_table_offs).bitwiseAnd(0xffff)
408-
return item_offs + p * pagesize
392+
override save(File file, String password, String password2) throws Exception {
393+
throw new UnsupportedOperationException("Writing wallet.dat is not supported")
409394
}
395+
}
410396

411-
private def readPrevPgno(int pgno) throws IOException {
412-
return readIntAt(pagesize * pgno + 12)
397+
abstract class BerkeleyDBPage {
398+
// page types
399+
static val P_LBTREE = 5 /* Btree leaf. */
400+
401+
protected var ByteBuffer b
402+
protected var RandomAccessFile raf
403+
protected var int pgno
404+
protected var long pgsize
405+
406+
new (RandomAccessFile raf, int pgno, long pgsize) throws IOException {
407+
this.raf = raf
408+
this.pgno = pgno
409+
this.pgsize = pgsize
410+
b = raf.channel.map(FileChannel.MapMode.READ_ONLY, pgno * pgsize, pgsize).order(ByteOrder.LITTLE_ENDIAN)
413411
}
414412

415-
private def readNextPgno(int pgno) throws IOException {
416-
return readIntAt(pagesize * pgno + 16)
413+
def getPgno() {
414+
pgno
417415
}
418416

419-
private def readEntryCount(int pgno) throws IOException {
420-
return readShortAt(pagesize * pgno + 20)
417+
def getPrevPgno() {
418+
b.getInt(12)
421419
}
422420

423-
private def readPageType(int pgno) throws IOException {
424-
return readByteAt(pagesize * pgno + 25)
421+
def getNextPgno() {
422+
b.getInt(16)
425423
}
426424

427-
private def readLastPgno() throws IOException {
428-
return readIntAt(32)
425+
def isRootPage() {
426+
prevPgno == 0
429427
}
430428

431-
private def readMagic() throws IOException {
432-
return readIntAt(12)
429+
def hasNextPage() {
430+
nextPgno != 0
433431
}
434432

435-
private def readVersion() throws IOException {
436-
return readIntAt(16)
433+
def isLBTREE() {
434+
pageType == P_LBTREE
437435
}
438436

439-
private def readPageSize() throws IOException {
440-
return readIntAt(20)
437+
def getPageType() {
438+
b.get(25)
441439
}
440+
}
442441

442+
/**
443+
* this is a leaf page, it contains all the data
444+
*/
445+
class BerkeleyDBLeafPage extends BerkeleyDBPage {
446+
static val SIZE_LEAF_HEADER = 26
443447

444-
//
445-
// ************* random access file reading, using LITTLE endian
446-
//
448+
new(RandomAccessFile raf, int pgno, long pgsize) throws IOException {
449+
super(raf, pgno, pgsize)
450+
}
447451

448-
private def byte[] readByteArrayAt(long offset, int size) throws IOException {
449-
val result = newByteArrayOfSize(size)
450-
f.seek(offset)
451-
f.read(result)
452-
return result
452+
/**
453+
* Right after the header of a b-tree leaf page there is a
454+
* table with 16bit words, each data item has an entry in this
455+
* table. They are the offsets of the actual data (relative to
456+
* the start of the page). Given a page buffer of a b-tree
457+
* page and an item index this function will look up the table
458+
* and return the offset (relative to page start) of this item.
459+
*/
460+
private def getItemOffset(int index) {
461+
val lookup_table_offs = SIZE_LEAF_HEADER + 2 * index
462+
b.getShort(lookup_table_offs).bitwiseAnd(0xffff)
453463
}
454464

455-
private def int readIntAt(long offset) throws IOException {
456-
f.seek(offset)
457-
bb4.clear
458-
f.channel.read(bb4)
459-
bb4.flip
460-
return bb4.int
465+
private def getBytes(int offset, int size){
466+
val a = newByteArrayOfSize(size)
467+
b.position(offset)
468+
b.get(a)
469+
ByteBuffer.wrap(a).order(ByteOrder.LITTLE_ENDIAN)
461470
}
462471

463-
private def short readShortAt(long offset) throws IOException {
464-
f.seek(offset)
465-
bb2.clear
466-
f.channel.read(bb2)
467-
bb2.flip
468-
return bb2.short
472+
def getItemType(int index){
473+
val offset = getItemOffset(index)
474+
b.get(offset + 2)
469475
}
470476

471-
private def byte readByteAt(long offset) throws IOException {
472-
f.seek(offset)
473-
return f.readByte
477+
def getItemData(int index){
478+
val offset = getItemOffset(index)
479+
val size = b.getShort(offset).bitwiseAnd(0xffff)
480+
getBytes(offset + 3, size)
474481
}
475482

483+
def getNextLeafPage() throws IOException {
484+
new BerkeleyDBLeafPage(raf, nextPgno, pgsize)
485+
}
476486

477-
//
478-
// ************* saving (won't be implemented)
479-
//
487+
def getEntryCount() {
488+
b.getShort(20)
489+
}
480490

481-
override save(File file, String password, String password2) throws Exception {
482-
throw new UnsupportedOperationException("Writing wallet.dat is not supported")
491+
}
492+
493+
/**
494+
* this is only used for page 0 to read magic, version, page size, etc.
495+
*/
496+
class BerkeleyDBHeaderPage extends BerkeleyDBPage {
497+
static val SIZE_METADATA_HEADER = 72
498+
499+
new (RandomAccessFile raf) throws IOException {
500+
super(raf, 0, SIZE_METADATA_HEADER)
483501
}
484502

503+
def getLastPgno() {
504+
b.getInt(32)
505+
}
485506

486-
//
487-
// ************* helpers
488-
//
507+
def getMagic() {
508+
b.getInt(12)
509+
}
489510

490-
}
511+
def getVersion() {
512+
b.getInt(16)
513+
}
491514

515+
def getPageSize() {
516+
b.getInt(20)
517+
}
518+
}
492519

493520

494521
/**

0 commit comments

Comments
 (0)