self_encryption_nodejs/
lib.rs1use napi::JsBuffer;
12use napi::JsObject;
13use napi::NapiRaw;
14use napi::Result;
15use napi::Status;
16use napi::bindgen_prelude::*;
17use napi_derive::napi;
18use self_encryption::bytes::Bytes;
19use self_encryption::xor_name::XOR_NAME_LEN;
20use std::path::Path;
21
22pub mod util;
23
24fn map_error<E>(err: E) -> napi::Error
26where
27 E: std::error::Error,
28{
29 let mut err_str = String::new();
30 err_str.push_str(&format!("{err:?}: {err}\n"));
31 let mut source = err.source();
32 while let Some(err) = source {
33 err_str.push_str(&format!(" Caused by: {err:?}: {err}\n"));
34 source = err.source();
35 }
36
37 napi::Error::new(Status::GenericFailure, err_str)
38}
39
40fn try_from_big_int<T: TryFrom<u64>>(value: BigInt, arg: &str) -> Result<T> {
41 let (_signed, value, losless) = value.get_u64();
42 if losless {
43 if let Ok(value) = T::try_from(value) {
44 return Ok(value);
45 }
46 }
47
48 Err(napi::Error::new(
49 Status::InvalidArg,
50 format!(
51 "expected `{arg}` to fit in a {}",
52 std::any::type_name::<T>()
53 ),
54 ))
55}
56
57#[napi]
59pub const MIN_CHUNK_SIZE: usize = self_encryption::MIN_CHUNK_SIZE;
60#[napi]
62pub const MAX_CHUNK_SIZE: usize = self_encryption::MAX_CHUNK_SIZE;
63#[napi]
65pub const COMPRESSION_QUALITY: i32 = self_encryption::COMPRESSION_QUALITY;
66#[napi]
68pub const MIN_ENCRYPTABLE_BYTES: usize = self_encryption::MIN_ENCRYPTABLE_BYTES;
69
70#[napi]
79#[derive(Clone)]
80pub struct XorName(self_encryption::XorName);
81
82#[napi]
83impl XorName {
84 #[napi(factory)]
86 pub fn from_content(content: Uint8Array) -> Self {
87 Self(self_encryption::XorName::from_content(content.as_ref()))
88 }
89
90 #[napi]
92 pub fn as_bytes(&self) -> Uint8Array {
93 Uint8Array::from(self.0.0.to_vec())
94 }
95
96 #[napi]
97 pub fn to_hex(&self) -> String {
98 hex::encode(self.0.0)
99 }
100
101 #[napi]
102 pub fn from_hex(hex: String) -> Result<Self> {
103 let mut bytes = [0u8; XOR_NAME_LEN];
104 hex::decode_to_slice(hex, &mut bytes)
105 .map_err(|e| napi::Error::new(Status::InvalidArg, e.to_string()))?;
106 Ok(Self(self_encryption::XorName(bytes)))
107 }
108}
109
110#[napi]
118pub struct ChunkInfo(self_encryption::ChunkInfo);
119
120#[napi]
121impl ChunkInfo {
122 #[napi(constructor)]
123 pub fn new(index: u32, dst_hash: &XorName, src_hash: &XorName, src_size: u32) -> Self {
124 Self(self_encryption::ChunkInfo {
125 index: index as usize,
126 dst_hash: dst_hash.0,
127 src_hash: src_hash.0,
128 src_size: src_size as usize,
129 })
130 }
131
132 #[napi(getter)]
133 pub fn index(&self) -> u32 {
134 self.0.index as u32
135 }
136
137 #[napi(getter)]
138 pub fn dst_hash(&self) -> XorName {
139 XorName(self.0.dst_hash)
140 }
141
142 #[napi(getter)]
143 pub fn src_hash(&self) -> XorName {
144 XorName(self.0.src_hash)
145 }
146
147 #[napi(getter)]
148 pub fn src_size(&self) -> u32 {
149 self.0.src_size as u32
150 }
151}
152
153#[napi]
158pub struct DataMap(self_encryption::DataMap);
159
160#[napi]
161#[allow(clippy::len_without_is_empty)]
162impl DataMap {
163 #[napi]
169 pub fn new(keys: Vec<&ChunkInfo>) -> Self {
170 Self(self_encryption::DataMap::new(
171 keys.iter().map(|ci| ci.0.clone()).collect(),
172 ))
173 }
174
175 #[napi]
177 pub fn with_child(keys: Vec<&ChunkInfo>, child: BigInt) -> Result<Self> {
178 let child = try_from_big_int(child, "child")?;
179
180 Ok(Self(self_encryption::DataMap::with_child(
181 keys.iter().map(|ci| ci.0.clone()).collect(),
182 child,
183 )))
184 }
185
186 #[napi]
188 pub fn original_file_size(&self) -> usize {
189 self.0.original_file_size()
190 }
191
192 #[napi]
194 pub fn infos(&self) -> Vec<ChunkInfo> {
195 self.0.infos().into_iter().map(ChunkInfo).collect()
196 }
197
198 #[napi]
200 pub fn child(&self) -> Option<usize> {
201 self.0.child
202 }
203
204 #[napi]
206 pub fn len(&self) -> usize {
207 self.0.len()
208 }
209
210 #[napi]
212 pub fn is_child(&self) -> bool {
213 self.0.is_child()
214 }
215}
216
217#[napi]
223#[derive(Clone)]
224pub struct EncryptedChunk(self_encryption::EncryptedChunk);
225
226#[napi]
227impl EncryptedChunk {
228 #[napi]
230 pub fn content_size(&self) -> u32 {
231 self.0.content.len() as u32
232 }
233
234 #[napi]
236 pub fn hash(&self) -> Uint8Array {
237 Uint8Array::from(
238 self_encryption::XorName::from_content(&self.0.content)
239 .0
240 .to_vec(),
241 )
242 }
243
244 #[napi]
246 pub fn content(&self) -> Uint8Array {
247 Uint8Array::from(self.0.content.to_vec())
248 }
249}
250
251#[napi]
256pub fn encrypt(data: Uint8Array) -> Result<EncryptResult> {
257 let data = Bytes::copy_from_slice(data.as_ref());
258 let (data_map, chunks) = self_encryption::encrypt(data).map_err(map_error)?;
259
260 Ok(EncryptResult { data_map, chunks })
261}
262
263#[napi]
265pub fn decrypt(data_map: &DataMap, chunks: Vec<&EncryptedChunk>) -> Result<Uint8Array> {
266 let inner_chunks = chunks
267 .into_iter()
268 .map(|chunk| chunk.0.clone())
269 .collect::<Vec<_>>();
270
271 let bytes = self_encryption::decrypt(&data_map.0, &inner_chunks).map_err(map_error)?;
272
273 Ok(Uint8Array::from(bytes.to_vec()))
274}
275
276#[napi]
281pub fn decrypt_from_storage(
282 env: Env,
283 data_map: &DataMap,
284 output_file: String,
285 #[napi(ts_arg_type = "(xorName: XorName) => Uint8Array")] get_chunk: JsFunction,
286) -> Result<()> {
287 let output_path = Path::new(&output_file);
288
289 let get_chunk_wrapper = |xor_name: self_encryption::XorName| -> self_encryption::Result<Bytes> {
290 let xor_name = XorName(xor_name.clone());
291 let xor_name = unsafe { XorName::to_napi_value(env.raw(), xor_name) }.unwrap();
292 let xor_name = unsafe { napi::JsUnknown::from_napi_value(env.raw(), xor_name) }.unwrap();
293
294 let result = get_chunk.call(None, &[xor_name]).map_err(|e| {
296 self_encryption::Error::Generic(format!("`getChunk` call resulted in error: {e}\n"))
297 })?;
298
299 let data =
300 unsafe { Uint8Array::from_napi_value(env.raw(), result.raw()) }.map_err(|e| {
301 self_encryption::Error::Generic(format!(
302 "Could not convert getChunk result to Uint8Array: {e}\n"
303 ))
304 })?;
305
306 Ok(Bytes::copy_from_slice(data.as_ref()))
307 };
308
309 self_encryption::decrypt_from_storage(&data_map.0, output_path, get_chunk_wrapper)
310 .map_err(map_error)
311}
312
313#[napi]
318pub fn streaming_decrypt_from_storage(
319 env: Env,
320 data_map: &DataMap,
321 output_file: String,
322 #[napi(ts_arg_type = "(xorNames: XorName[]) => Uint8Array")] get_chunk_parallel: JsFunction,
323) -> Result<()> {
324 let output_path = Path::new(&output_file);
325
326 let get_chunk_parallel_wrapper =
327 |xor_name: &[self_encryption::XorName]| -> self_encryption::Result<Vec<Bytes>> {
328 let xor_names = xor_name
330 .iter()
331 .map(|xor_name| {
332 let xor_name = XorName(xor_name.clone());
333 let xor_name = unsafe { XorName::to_napi_value(env.raw(), xor_name) }.unwrap();
334 unsafe { napi::JsUnknown::from_napi_value(env.raw(), xor_name) }.unwrap()
335 })
336 .collect::<Vec<_>>();
337
338 let xor_names = Array::from_vec(&env, xor_names)
340 .map_err(|e| {
341 self_encryption::Error::Generic(format!("Could not create array: {e}\n"))
342 })?
343 .coerce_to_object()
345 .map_err(|e| {
346 self_encryption::Error::Generic(format!("Could not create array: {e}\n"))
347 })?
348 .into_unknown();
349
350 let result = get_chunk_parallel.call(None, &[xor_names]).map_err(|e| {
352 self_encryption::Error::Generic(format!(
353 "`getChunkParallel` call resulted in error: {e}\n"
354 ))
355 })?;
356
357 let data =
358 unsafe { JsObject::from_napi_value(env.raw(), result.raw()) }.map_err(|e| {
359 self_encryption::Error::Generic(format!(
360 "Could not convert getChunkParallel result to Array: {e}\n"
361 ))
362 })?;
363
364 let mut data_vec = vec![];
365 for i in 0..data.get_array_length().map_err(|e| {
366 self_encryption::Error::Generic(format!(
367 "Expect getChunkParallel to return array: {e}\n"
368 ))
369 })? {
370 let item = data.get_element::<JsBuffer>(i).map_err(|e| {
371 self_encryption::Error::Generic(format!(
372 "Could not get element from getChunkParallel result: {e}\n"
373 ))
374 })?;
375 let item = item.into_ref().map_err(|e| {
376 self_encryption::Error::Generic(format!(
377 "Could not get element from getChunkParallel result: {e}\n"
378 ))
379 })?;
380
381 data_vec.push(Bytes::copy_from_slice(item.as_ref()));
382 }
383
384 Ok(data_vec)
385 };
386
387 self_encryption::streaming_decrypt_from_storage(
388 &data_map.0,
389 output_path,
390 get_chunk_parallel_wrapper,
391 )
392 .map_err(map_error)
393}
394
395#[napi]
397pub fn streaming_encrypt_from_file(
398 env: Env,
399 file_path: String,
400 #[napi(ts_arg_type = "(xorName: XorName, bytes: Uint8Array) => undefined")]
401 chunk_store: JsFunction,
402) -> Result<DataMap> {
403 let file_path = Path::new(&file_path);
404
405 let chunk_store_wrapper = |xor_name: self_encryption::XorName,
406 bytes: Bytes|
407 -> self_encryption::Result<()> {
408 let xor_name = XorName(xor_name);
409 let xor_name = unsafe { XorName::to_napi_value(env.raw(), xor_name) }.unwrap();
410 let xor_name = unsafe { napi::JsUnknown::from_napi_value(env.raw(), xor_name) }.unwrap();
411
412 let bytes = Uint8Array::from(bytes.to_vec());
413 let bytes = unsafe { Uint8Array::to_napi_value(env.raw(), bytes) }.unwrap();
414 let bytes = unsafe { napi::JsUnknown::from_napi_value(env.raw(), bytes) }.unwrap();
415
416 let _ = chunk_store.call(None, &[xor_name, bytes]).map_err(|e| {
417 self_encryption::Error::Generic(format!("`chunkStore` call resulted in error: {e}\n"))
418 })?;
419
420 Ok(())
421 };
422
423 self_encryption::streaming_encrypt_from_file(file_path, chunk_store_wrapper)
424 .map(|dm| DataMap(dm))
425 .map_err(map_error)
426}
427
428#[napi]
433pub fn encrypt_from_file(input_file: String, output_dir: String) -> Result<EncryptFromFileResult> {
434 let input_path = Path::new(&input_file);
435 let output_path = Path::new(&output_dir);
436
437 let (data_map, chunk_names) =
438 self_encryption::encrypt_from_file(input_path, output_path).map_err(map_error)?;
439
440 let chunk_names = chunk_names.iter().map(|name| hex::encode(name.0)).collect();
441
442 Ok(EncryptFromFileResult {
443 data_map,
444 chunk_names,
445 })
446}
447
448#[napi]
450pub struct EncryptFromFileResult {
451 pub(crate) data_map: self_encryption::DataMap,
452 pub(crate) chunk_names: Vec<String>,
453}
454
455#[napi]
456impl EncryptFromFileResult {
457 #[napi(getter)]
458 pub fn data_map(&self) -> DataMap {
459 DataMap(self.data_map.clone())
460 }
461
462 #[napi(getter)]
463 pub fn chunk_names(&self) -> Vec<String> {
464 self.chunk_names.clone()
465 }
466}
467
468#[napi]
470pub struct EncryptResult {
471 pub(crate) data_map: self_encryption::DataMap,
472 pub(crate) chunks: Vec<self_encryption::EncryptedChunk>,
473}
474
475#[napi]
476impl EncryptResult {
477 #[napi(getter)]
478 pub fn data_map(&self) -> DataMap {
479 DataMap(self.data_map.clone())
480 }
481
482 #[napi(getter)]
483 pub fn chunks(&self) -> Vec<EncryptedChunk> {
484 self.chunks
485 .iter()
486 .map(|chunk| EncryptedChunk(chunk.clone()))
487 .collect()
488 }
489}