use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::PathBuf;
use crate::error::BlkReaderError;
pub struct BlkReader {
blocks_dir: PathBuf,
buffer_size: usize,
open_files: HashMap<u32, BufReader<File>>,
access_order: Vec<u32>,
xor_key: [u8; 8],
xor_key_len: usize,
}
const MAX_OPEN_FILES: usize = 8;
const MAINNET_MAGIC: u32 = 0xD9B4BEF9;
impl BlkReader {
pub fn new(blocks_dir: PathBuf, buffer_size: usize) -> Self {
let (xor_key, xor_key_len) = Self::load_xor_key(&blocks_dir);
if xor_key_len > 0 {
tracing::info!(
key_hex = %hex::encode(&xor_key[..xor_key_len]),
"BlkReader: xor.dat detected — Bitcoin Core 28.0+ XOR encoding active"
);
}
Self {
blocks_dir,
buffer_size,
open_files: HashMap::new(),
access_order: Vec::new(),
xor_key,
xor_key_len,
}
}
fn load_xor_key(blocks_dir: &std::path::Path) -> ([u8; 8], usize) {
let xor_path = blocks_dir.join("xor.dat");
match std::fs::read(&xor_path) {
Ok(data) if !data.is_empty() => {
let len = data.len().min(8);
let mut key = [0u8; 8];
key[..len].copy_from_slice(&data[..len]);
(key, len)
}
_ => ([0u8; 8], 0),
}
}
pub fn read_block_at(&mut self, n_file: u32, n_data_pos: u32) -> Result<Vec<u8>, BlkReaderError> {
if n_data_pos < 8 {
return Err(BlkReaderError::InvalidMagicBytes {
file: n_file,
offset: n_data_pos as u64,
got: 0,
});
}
let header_pos = (n_data_pos - 8) as u64;
let xor_key = self.xor_key;
let xor_key_len = self.xor_key_len;
let reader = self.get_or_open(n_file)?;
reader.seek(SeekFrom::Start(header_pos))?;
let mut magic_buf = [0u8; 4];
reader.read_exact(&mut magic_buf)?;
if xor_key_len > 0 {
for (i, byte) in magic_buf.iter_mut().enumerate() {
*byte ^= xor_key[(header_pos as usize + i) % xor_key_len];
}
}
let magic = u32::from_le_bytes(magic_buf);
if magic != MAINNET_MAGIC {
let mut diag_buf = [0u8; 32];
reader.seek(SeekFrom::Start(n_data_pos as u64))?;
let _ = reader.read_exact(&mut diag_buf);
if xor_key_len > 0 {
for (i, byte) in diag_buf.iter_mut().enumerate() {
*byte ^= xor_key[(n_data_pos as usize + i) % xor_key_len];
}
}
tracing::error!(
file = n_file,
offset = n_data_pos,
hex = %hex::encode(diag_buf),
"ERROR: Magic bytes not found. Content at original offset above."
);
return Err(BlkReaderError::InvalidMagicBytes {
file: n_file,
offset: n_data_pos as u64,
got: magic,
});
}
let mut size_buf = [0u8; 4];
reader.read_exact(&mut size_buf)?;
if xor_key_len > 0 {
let size_pos = header_pos as usize + 4;
for (i, byte) in size_buf.iter_mut().enumerate() {
*byte ^= xor_key[(size_pos + i) % xor_key_len];
}
}
let block_size = u32::from_le_bytes(size_buf) as usize;
let mut block_data = vec![0u8; block_size];
reader.read_exact(&mut block_data)?;
if xor_key_len > 0 {
let data_pos = n_data_pos as usize;
for (i, byte) in block_data.iter_mut().enumerate() {
*byte ^= xor_key[(data_pos + i) % xor_key_len];
}
}
if let Some(pos) = self.access_order.iter().position(|&f| f == n_file) {
self.access_order.remove(pos);
}
self.access_order.push(n_file);
Ok(block_data)
}
fn get_or_open(&mut self, n_file: u32) -> Result<&mut BufReader<File>, BlkReaderError> {
if !self.open_files.contains_key(&n_file) {
if self.open_files.len() >= MAX_OPEN_FILES {
if let Some(oldest) = self.access_order.first().copied() {
self.open_files.remove(&oldest);
self.access_order.retain(|&f| f != oldest);
}
}
let path = self.blocks_dir.join(format!("blk{n_file:05}.dat"));
if !path.exists() {
return Err(BlkReaderError::BlkFileNotFound { index: n_file });
}
let file = File::open(&path)?;
let reader = BufReader::with_capacity(self.buffer_size, file);
self.open_files.insert(n_file, reader);
self.access_order.push(n_file);
}
Ok(self.open_files.get_mut(&n_file).unwrap())
}
}