feat: move progress reporting to rust side
This commit is contained in:
parent
6f477f9cbc
commit
5e354fc7ae
2 changed files with 158 additions and 105 deletions
73
src/lib.rs
73
src/lib.rs
|
@ -271,8 +271,6 @@ fn get_frame_length(
|
||||||
header_version: Option<u32>,
|
header_version: Option<u32>,
|
||||||
) -> Result<Option<u32>, JsValue> {
|
) -> Result<Option<u32>, JsValue> {
|
||||||
if reader.remaining_length() < 4 {
|
if reader.remaining_length() < 4 {
|
||||||
web_sys::console::log_1(&"too less data to decrypt frame length".into());
|
|
||||||
|
|
||||||
return Ok(None); // Not enough data to read the frame length
|
return Ok(None); // Not enough data to read the frame length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,7 +352,10 @@ pub struct BackupDecryptor {
|
||||||
database_bytes: Vec<u8>,
|
database_bytes: Vec<u8>,
|
||||||
ciphertext_buf: Vec<u8>,
|
ciphertext_buf: Vec<u8>,
|
||||||
plaintext_buf: Vec<u8>,
|
plaintext_buf: Vec<u8>,
|
||||||
total_bytes_received: usize,
|
total_file_size: usize,
|
||||||
|
total_bytes_processed: usize,
|
||||||
|
processed_percentage: usize,
|
||||||
|
progress_callback: Option<js_sys::Function>,
|
||||||
is_initialized: bool,
|
is_initialized: bool,
|
||||||
// this is stored if the frame has been decrypted but it is an attachment for which we don't have enough data available
|
// this is stored if the frame has been decrypted but it is an attachment for which we don't have enough data available
|
||||||
// so we don't need to decrypt the whole frame again
|
// so we don't need to decrypt the whole frame again
|
||||||
|
@ -375,7 +376,10 @@ impl BackupDecryptor {
|
||||||
database_bytes: Vec::new(),
|
database_bytes: Vec::new(),
|
||||||
ciphertext_buf: Vec::new(),
|
ciphertext_buf: Vec::new(),
|
||||||
plaintext_buf: Vec::new(),
|
plaintext_buf: Vec::new(),
|
||||||
total_bytes_received: 0,
|
total_file_size: 0,
|
||||||
|
total_bytes_processed: 0,
|
||||||
|
processed_percentage: 0,
|
||||||
|
progress_callback: None,
|
||||||
is_initialized: false,
|
is_initialized: false,
|
||||||
current_backup_frame: None,
|
current_backup_frame: None,
|
||||||
}
|
}
|
||||||
|
@ -389,10 +393,41 @@ impl BackupDecryptor {
|
||||||
new_data.extend_from_slice(self.reader.remaining_data());
|
new_data.extend_from_slice(self.reader.remaining_data());
|
||||||
new_data.extend_from_slice(chunk);
|
new_data.extend_from_slice(chunk);
|
||||||
|
|
||||||
self.total_bytes_received += chunk.len();
|
// self.total_bytes_received += chunk.len();
|
||||||
self.reader = ByteReader::new(new_data);
|
self.reader = ByteReader::new(new_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn set_progress_callback(
|
||||||
|
&mut self,
|
||||||
|
total_file_size: usize,
|
||||||
|
progress_callback: js_sys::Function,
|
||||||
|
) {
|
||||||
|
self.total_file_size = total_file_size;
|
||||||
|
self.progress_callback = Some(progress_callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call_progress_callback(&self) -> usize {
|
||||||
|
let prev_percentage = self.processed_percentage;
|
||||||
|
|
||||||
|
if let Some(ref progress_callback) = self.progress_callback {
|
||||||
|
let percentage =
|
||||||
|
(self.total_bytes_processed as f32 / self.total_file_size as f32) * 100.0;
|
||||||
|
|
||||||
|
let rounded = percentage.round() as usize;
|
||||||
|
|
||||||
|
if rounded != prev_percentage {
|
||||||
|
progress_callback
|
||||||
|
.call1(&JsValue::NULL, &JsValue::from(rounded))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
return rounded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev_percentage;
|
||||||
|
}
|
||||||
|
|
||||||
// process available data
|
// process available data
|
||||||
// returns Ok if the decryption of the current frame was successful
|
// returns Ok if the decryption of the current frame was successful
|
||||||
// Ok(false) if there is enough data left
|
// Ok(false) if there is enough data left
|
||||||
|
@ -431,6 +466,10 @@ impl BackupDecryptor {
|
||||||
if self.reader.remaining_length() < length as usize {
|
if self.reader.remaining_length() < length as usize {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
} else {
|
} else {
|
||||||
|
self.total_bytes_processed += (length + 10) as usize;
|
||||||
|
let new_percentage = self.call_progress_callback();
|
||||||
|
self.processed_percentage = new_percentage;
|
||||||
|
|
||||||
// attachments are encoded as length, which would have to be read using decode_frame_payload
|
// attachments are encoded as length, which would have to be read using decode_frame_payload
|
||||||
// +10 because in decrypt_frame_payload we would read `their_mac` from reader which is 10 bytes long
|
// +10 because in decrypt_frame_payload we would read `their_mac` from reader which is 10 bytes long
|
||||||
self.reader.increment_position((length + 10) as usize);
|
self.reader.increment_position((length + 10) as usize);
|
||||||
|
@ -442,8 +481,7 @@ impl BackupDecryptor {
|
||||||
|
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// we need to do this here so that during get_frame_length and decrypt_frame we use the same hmac and ctr
|
// we need to do this here so that during get_frame_length and decrypt_frame we use the same hmac and ctr
|
||||||
let mut hmac = <HmacSha256 as Mac>::new_from_slice(&keys.hmac_key)
|
let mut hmac = <HmacSha256 as Mac>::new_from_slice(&keys.hmac_key)
|
||||||
.map_err(|_| JsValue::from_str("Invalid HMAC key"))?;
|
.map_err(|_| JsValue::from_str("Invalid HMAC key"))?;
|
||||||
|
@ -453,8 +491,12 @@ impl BackupDecryptor {
|
||||||
|
|
||||||
let initial_reader_position = self.reader.get_position();
|
let initial_reader_position = self.reader.get_position();
|
||||||
|
|
||||||
let frame_length =
|
let frame_length = match get_frame_length(
|
||||||
match get_frame_length(&mut self.reader, &mut hmac, &mut ctr, header_data.version) {
|
&mut self.reader,
|
||||||
|
&mut hmac,
|
||||||
|
&mut ctr,
|
||||||
|
header_data.version,
|
||||||
|
) {
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
// need to reset the position here because getting the length and decrypting the frame rely on
|
// need to reset the position here because getting the length and decrypting the frame rely on
|
||||||
// the same hmac / ctr and if we don't read the position first they won't be correct
|
// the same hmac / ctr and if we don't read the position first they won't be correct
|
||||||
|
@ -479,6 +521,11 @@ impl BackupDecryptor {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
Ok(Some(backup_frame)) => {
|
Ok(Some(backup_frame)) => {
|
||||||
|
// +4 because the length which was read is 4 bytes long
|
||||||
|
self.total_bytes_processed += (frame_length + 4) as usize;
|
||||||
|
let new_percentage = self.call_progress_callback();
|
||||||
|
self.processed_percentage = new_percentage;
|
||||||
|
|
||||||
// can not assign right here because of borrowing issues
|
// can not assign right here because of borrowing issues
|
||||||
let mut new_iv = increment_initialisation_vector(iv);
|
let mut new_iv = increment_initialisation_vector(iv);
|
||||||
|
|
||||||
|
@ -518,7 +565,8 @@ impl BackupDecryptor {
|
||||||
self.database_bytes.push(b';');
|
self.database_bytes.push(b';');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if backup_frame.preference.is_some() || backup_frame.key_value.is_some() {
|
} else if backup_frame.preference.is_some() || backup_frame.key_value.is_some()
|
||||||
|
{
|
||||||
} else {
|
} else {
|
||||||
// we just skip these types here
|
// we just skip these types here
|
||||||
let backup_frame_cloned = backup_frame.clone();
|
let backup_frame_cloned = backup_frame.clone();
|
||||||
|
@ -542,6 +590,10 @@ impl BackupDecryptor {
|
||||||
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
} else {
|
} else {
|
||||||
|
self.total_bytes_processed += (length + 10) as usize;
|
||||||
|
let new_percentage = self.call_progress_callback();
|
||||||
|
self.processed_percentage = new_percentage;
|
||||||
|
|
||||||
// attachments are encoded as length, which would have to be read using decode_frame_payload
|
// attachments are encoded as length, which would have to be read using decode_frame_payload
|
||||||
// +10 because in decrypt_frame_payload we would read `their_mac` from reader which is 10 bytes long
|
// +10 because in decrypt_frame_payload we would read `their_mac` from reader which is 10 bytes long
|
||||||
self.reader.increment_position((length + 10) as usize);
|
self.reader.increment_position((length + 10) as usize);
|
||||||
|
@ -565,6 +617,7 @@ impl BackupDecryptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn finish(self) -> Result<DecryptionResult, JsValue> {
|
pub fn finish(self) -> Result<DecryptionResult, JsValue> {
|
||||||
|
|
20
test/dist/index.js
vendored
20
test/dist/index.js
vendored
|
@ -14,21 +14,17 @@ async function initialize() {
|
||||||
export async function decryptBackup(file, passphrase, progressCallback) {
|
export async function decryptBackup(file, passphrase, progressCallback) {
|
||||||
await initialize();
|
await initialize();
|
||||||
|
|
||||||
console.log("Starting decryption of file size:", file.size);
|
const fileSize = file.size;
|
||||||
|
console.log("Starting decryption of file size:", fileSize);
|
||||||
const decryptor = new BackupDecryptor();
|
const decryptor = new BackupDecryptor();
|
||||||
|
decryptor.set_progress_callback(fileSize, (percentage) =>
|
||||||
|
console.info(`${percentage}% done`),
|
||||||
|
);
|
||||||
const chunkSize = 1024 * 1024 * 40; // 40MB chunks
|
const chunkSize = 1024 * 1024 * 40; // 40MB chunks
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
let percent;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (offset < file.size) {
|
while (offset < file.size) {
|
||||||
const newPercent = Math.round((100 / file.size) * offset);
|
|
||||||
|
|
||||||
if (newPercent !== percent) {
|
|
||||||
percent = newPercent;
|
|
||||||
console.info(`${percent}% done`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log(`Processing chunk at offset ${offset}`);
|
// console.log(`Processing chunk at offset ${offset}`);
|
||||||
const chunk = file.slice(offset, offset + chunkSize);
|
const chunk = file.slice(offset, offset + chunkSize);
|
||||||
const arrayBuffer = await chunk.arrayBuffer();
|
const arrayBuffer = await chunk.arrayBuffer();
|
||||||
|
@ -59,7 +55,11 @@ export async function decryptBackup(file, passphrase, progressCallback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log("All chunks processed, finishing up");
|
// console.log("All chunks processed, finishing up");
|
||||||
return decryptor.finish();
|
const result = decryptor.finish();
|
||||||
|
|
||||||
|
decryptor.free();
|
||||||
|
|
||||||
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Decryption failed:", e);
|
console.error("Decryption failed:", e);
|
||||||
throw e;
|
throw e;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue