nj_core/
bigint.rs

1use std::ptr;
2
3use tracing::trace;
4
5use crate::TryIntoJs;
6use crate::JSValue;
7use crate::sys::napi_value;
8use crate::val::JsEnv;
9use crate::NjError;
10
11pub use num_bigint::*;
12
13impl<'a> JSValue<'a> for BigInt {
14    #[allow(clippy::not_unsafe_ptr_arg_deref)]
15    fn convert_to_rust(env: &'a JsEnv, js_value: napi_value) -> Result<Self, NjError> {
16        trace!("Converting JS BigInt to Rust!");
17
18        env.assert_type(js_value, crate::sys::napi_valuetype_napi_bigint)?;
19        let mut word_count = 0_usize;
20
21        // https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words
22        // Frist call is to figure out how long of a vec to make.
23        crate::napi_call_result!(crate::sys::napi_get_value_bigint_words(
24            env.inner(),
25            js_value,
26            ptr::null_mut(),
27            &mut word_count,
28            ptr::null_mut(),
29        ))?;
30
31        // Now we actually get the sign and the vector.
32        let mut napi_buffer: Vec<u64> = vec![0; word_count];
33        let mut sign = 0;
34
35        crate::napi_call_result!(crate::sys::napi_get_value_bigint_words(
36            env.inner(),
37            js_value,
38            &mut sign,
39            &mut word_count,
40            napi_buffer.as_mut_ptr(),
41        ))?;
42
43        // BigInt is initialized via a little endian &[u8] so we need to build the u8s from the
44        // u64s
45        let mut bytes: Vec<u8> = Vec::new();
46        for i in &napi_buffer {
47            bytes.extend_from_slice(&i.to_le_bytes());
48        }
49
50        // The N-API documentation on the signs is lacking.
51        let sign = match sign {
52            0 => Sign::Plus,
53            1 => Sign::Minus,
54            _ => unreachable!(),
55        };
56        let res = BigInt::from_bytes_le(sign, &bytes);
57        trace!(
58            "Converted JS BigInt to Rust! words: {:#X?}, bytes: {:#?}, len: {:?}, bigint: {:#?}",
59            napi_buffer,
60            bytes,
61            bytes.len(),
62            res
63        );
64        Ok(res)
65    }
66}
67
68impl TryIntoJs for BigInt {
69    fn try_to_js(self, env: &JsEnv) -> Result<napi_value, NjError> {
70        let (sign, bytes) = self.to_bytes_le();
71        let mut words: Vec<u64> = Vec::new();
72        use std::cmp::min;
73
74        // bytes can be non-multiples of 8.
75        for i in 0..(bytes.len() / 8 + 1) {
76            let mut slice: [u8; 8] = [0; 8];
77
78            // https://stackoverflow.com/a/29784723 seems to be the least bad way to convert a Vec
79            // slice into an array :/
80            for (place, element) in slice
81                .iter_mut()
82                .zip(bytes[i * 8..min((i + 1) * 8, bytes.len())].iter())
83            {
84                *place = *element;
85            }
86            words.push(u64::from_le_bytes(slice));
87        }
88        let sign = match sign {
89            Sign::Minus => 1,
90            Sign::Plus | Sign::NoSign => 0,
91        };
92        let word_count = words.len();
93
94        trace!(
95            "Converted Rust BigInt to JS Bigint: {:#?}!, bytes: {:#?}, len: {:?}, words: {:#?}, word_count {:#?}, sign: {:#?}",
96            self,
97            bytes,
98            bytes.len(),
99            words,
100            word_count,
101            sign,
102        );
103
104        let mut napi_buffer = ptr::null_mut();
105
106        // https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_words
107        crate::napi_call_result!(crate::sys::napi_create_bigint_words(
108            env.inner(),
109            sign,
110            word_count,
111            words.as_ptr(),
112            &mut napi_buffer
113        ))?;
114        Ok(napi_buffer)
115    }
116}