@@ -9,6 +9,7 @@ import java.io.UnsupportedEncodingException
9
9
import java.math.BigInteger
10
10
import java.nio.ByteBuffer
11
11
import java.nio.ByteOrder
12
+ import java.nio.channels.FileChannel
12
13
import java.util.ArrayList
13
14
import java.util.Arrays
14
15
import java.util.HashMap
@@ -29,20 +30,6 @@ import prof7bit.bitcoin.wallettool.exceptions.FormatFoundNeedPasswordException
29
30
class WalletDatHandler extends AbstractImportExportHandler {
30
31
val log = LoggerFactory . getLogger(WalletDatHandler )
31
32
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
-
46
33
/**
47
34
* this list will contain all items from the bdb file,
48
35
* its alternating key and value data. It has an even
@@ -73,23 +60,17 @@ class WalletDatHandler extends AbstractImportExportHandler {
73
60
* db_page.h, db_dump and a hex editor.
74
61
*/
75
62
override load (File file , String password , String password2 ) throws Exception {
63
+ var RandomAccessFile raf
76
64
try {
77
65
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" )
86
67
87
- parseBerkeleyFile
68
+ parseBerkeleyFile(raf)
88
69
parseBitcoinData
89
70
decryptAndImport(password)
90
71
91
72
} finally {
92
- f . close
73
+ raf . close
93
74
}
94
75
}
95
76
@@ -341,6 +322,7 @@ class WalletDatHandler extends AbstractImportExportHandler {
341
322
// ************* reading Berkeley-specific structures from the file
342
323
//
343
324
325
+
344
326
/**
345
327
* Parse the wallet.dat file, find all b-tree leaf
346
328
* pages in the file and put all their items into
@@ -349,12 +331,26 @@ class WalletDatHandler extends AbstractImportExportHandler {
349
331
* no next page. When this function has returned we
350
332
* have all key/value items in the bdbKeyValueItems list.
351
333
*/
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
+
353
348
bdbKeyValueItems. clear
354
- for (p : 0 .. last_pgno) {
349
+ for (pgno : 0 .. last_pgno) {
355
350
// 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)
358
354
}
359
355
}
360
356
log. debug(" parsing done, found {} key/value pairs in db file" , bdbKeyValueItems. length / 2 )
@@ -366,129 +362,160 @@ class WalletDatHandler extends AbstractImportExportHandler {
366
362
* items into the bdbKeyValueItems list until
367
363
* there is no next page anymore.
368
364
*/
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
374
370
}
375
371
}
376
372
377
373
/**
378
374
* parse this leaf page and add all
379
375
* its items to the bdbKeyValueItems list
380
376
*/
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)
391
384
}
392
385
}
393
386
}
394
387
395
- static val START_TABLE = 26
388
+ //
389
+ // ************* saving (won't be implemented)
390
+ //
396
391
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" )
409
394
}
395
+ }
410
396
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 )
413
411
}
414
412
415
- private def readNextPgno ( int pgno ) throws IOException {
416
- return readIntAt(pagesize * pgno + 16 )
413
+ def getPgno () {
414
+ pgno
417
415
}
418
416
419
- private def readEntryCount ( int pgno ) throws IOException {
420
- return readShortAt(pagesize * pgno + 20 )
417
+ def getPrevPgno () {
418
+ b . getInt( 12 )
421
419
}
422
420
423
- private def readPageType ( int pgno ) throws IOException {
424
- return readByteAt(pagesize * pgno + 25 )
421
+ def getNextPgno () {
422
+ b . getInt( 16 )
425
423
}
426
424
427
- private def readLastPgno () throws IOException {
428
- return readIntAt( 32 )
425
+ def isRootPage () {
426
+ prevPgno == 0
429
427
}
430
428
431
- private def readMagic () throws IOException {
432
- return readIntAt( 12 )
429
+ def hasNextPage () {
430
+ nextPgno != 0
433
431
}
434
432
435
- private def readVersion () throws IOException {
436
- return readIntAt( 16 )
433
+ def isLBTREE () {
434
+ pageType == P_LBTREE
437
435
}
438
436
439
- private def readPageSize () throws IOException {
440
- return readIntAt( 20 )
437
+ def getPageType () {
438
+ b . get( 25 )
441
439
}
440
+ }
442
441
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
443
447
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
+ }
447
451
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 )
453
463
}
454
464
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 )
461
470
}
462
471
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 )
469
475
}
470
476
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)
474
481
}
475
482
483
+ def getNextLeafPage () throws IOException {
484
+ new BerkeleyDBLeafPage (raf, nextPgno, pgsize)
485
+ }
476
486
477
- //
478
- // ************* saving (won't be implemented )
479
- //
487
+ def getEntryCount () {
488
+ b . getShort( 20 )
489
+ }
480
490
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 )
483
501
}
484
502
503
+ def getLastPgno () {
504
+ b. getInt(32 )
505
+ }
485
506
486
- //
487
- // ************* helpers
488
- //
507
+ def getMagic () {
508
+ b . getInt( 12 )
509
+ }
489
510
490
- }
511
+ def getVersion () {
512
+ b. getInt(16 )
513
+ }
491
514
515
+ def getPageSize () {
516
+ b. getInt(20 )
517
+ }
518
+ }
492
519
493
520
494
521
/**
0 commit comments