1use std::collections::HashMap;
2use std::fs::File;
3use std::io::{BufReader, Read, Seek, SeekFrom};
4use std::path::PathBuf;
5
6use crate::error::BlkReaderError;
7
8pub struct BlkReader {
15 blocks_dir: PathBuf,
16 buffer_size: usize,
17 open_files: HashMap<u32, BufReader<File>>,
18 access_order: Vec<u32>,
19 xor_key: [u8; 8],
22 xor_key_len: usize,
23}
24
25const MAX_OPEN_FILES: usize = 8;
26const MAINNET_MAGIC: u32 = 0xD9B4BEF9;
27
28impl BlkReader {
29 pub fn new(blocks_dir: PathBuf, buffer_size: usize) -> Self {
35 let (xor_key, xor_key_len) = Self::load_xor_key(&blocks_dir);
38 if xor_key_len > 0 {
39 tracing::info!(
40 key_hex = %hex::encode(&xor_key[..xor_key_len]),
41 "BlkReader: xor.dat detected — Bitcoin Core 28.0+ XOR encoding active"
42 );
43 }
44 Self {
45 blocks_dir,
46 buffer_size,
47 open_files: HashMap::new(),
48 access_order: Vec::new(),
49 xor_key,
50 xor_key_len,
51 }
52 }
53
54 fn load_xor_key(blocks_dir: &std::path::Path) -> ([u8; 8], usize) {
57 let xor_path = blocks_dir.join("xor.dat");
58 match std::fs::read(&xor_path) {
59 Ok(data) if !data.is_empty() => {
60 let len = data.len().min(8);
61 let mut key = [0u8; 8];
62 key[..len].copy_from_slice(&data[..len]);
63 (key, len)
64 }
65 _ => ([0u8; 8], 0),
66 }
67 }
68
69 pub fn read_block_at(&mut self, n_file: u32, n_data_pos: u32) -> Result<Vec<u8>, BlkReaderError> {
84 if n_data_pos < 8 {
88 return Err(BlkReaderError::InvalidMagicBytes {
89 file: n_file,
90 offset: n_data_pos as u64,
91 got: 0,
92 });
93 }
94 let header_pos = (n_data_pos - 8) as u64;
95
96 let xor_key = self.xor_key;
99 let xor_key_len = self.xor_key_len;
100
101 let reader = self.get_or_open(n_file)?;
102 reader.seek(SeekFrom::Start(header_pos))?;
103
104 let mut magic_buf = [0u8; 4];
105 reader.read_exact(&mut magic_buf)?;
106 if xor_key_len > 0 {
108 for (i, byte) in magic_buf.iter_mut().enumerate() {
109 *byte ^= xor_key[(header_pos as usize + i) % xor_key_len];
110 }
111 }
112 let magic = u32::from_le_bytes(magic_buf);
113
114 if magic != MAINNET_MAGIC {
115 let mut diag_buf = [0u8; 32];
117 reader.seek(SeekFrom::Start(n_data_pos as u64))?;
118 let _ = reader.read_exact(&mut diag_buf);
119 if xor_key_len > 0 {
120 for (i, byte) in diag_buf.iter_mut().enumerate() {
121 *byte ^= xor_key[(n_data_pos as usize + i) % xor_key_len];
122 }
123 }
124 tracing::error!(
125 file = n_file,
126 offset = n_data_pos,
127 hex = %hex::encode(diag_buf),
128 "ERROR: Magic bytes not found. Content at original offset above."
129 );
130 return Err(BlkReaderError::InvalidMagicBytes {
131 file: n_file,
132 offset: n_data_pos as u64,
133 got: magic,
134 });
135 }
136
137 let mut size_buf = [0u8; 4];
139 reader.read_exact(&mut size_buf)?;
140 if xor_key_len > 0 {
141 let size_pos = header_pos as usize + 4;
142 for (i, byte) in size_buf.iter_mut().enumerate() {
143 *byte ^= xor_key[(size_pos + i) % xor_key_len];
144 }
145 }
146 let block_size = u32::from_le_bytes(size_buf) as usize;
147
148 let mut block_data = vec![0u8; block_size];
150 reader.read_exact(&mut block_data)?;
151 if xor_key_len > 0 {
152 let data_pos = n_data_pos as usize;
153 for (i, byte) in block_data.iter_mut().enumerate() {
154 *byte ^= xor_key[(data_pos + i) % xor_key_len];
155 }
156 }
157
158 if let Some(pos) = self.access_order.iter().position(|&f| f == n_file) {
159 self.access_order.remove(pos);
160 }
161 self.access_order.push(n_file);
162
163 Ok(block_data)
164 }
165
166 fn get_or_open(&mut self, n_file: u32) -> Result<&mut BufReader<File>, BlkReaderError> {
167 if !self.open_files.contains_key(&n_file) {
168 if self.open_files.len() >= MAX_OPEN_FILES {
169 if let Some(oldest) = self.access_order.first().copied() {
170 self.open_files.remove(&oldest);
171 self.access_order.retain(|&f| f != oldest);
172 }
173 }
174
175 let path = self.blocks_dir.join(format!("blk{n_file:05}.dat"));
176 if !path.exists() {
177 return Err(BlkReaderError::BlkFileNotFound { index: n_file });
178 }
179
180 let file = File::open(&path)?;
181 let reader = BufReader::with_capacity(self.buffer_size, file);
182 self.open_files.insert(n_file, reader);
183 self.access_order.push(n_file);
184 }
185
186 Ok(self.open_files.get_mut(&n_file).unwrap())
187 }
188}