use napi::bindgen_prelude::{Array, Buffer, FromNapiValue, Object, ToNapiValue, Unknown};
use napi::{sys, Env, Error, Result, Status, ValueType};
use vitaminc_aead::CipherText;
use crate::convert::{
define_own_property, eager_capacity, forbidden_key, get_property_unknown, own_enumerable_keys,
MAX_DEPTH,
};
const T_CIPHERTEXT: &str = "ct";
const T_NONE: &str = "none";
const T_SEQ: &str = "seq";
const T_EMPTY_SEQ: &str = "eseq";
const T_MAP: &str = "map";
const T_EMPTY_MAP: &str = "emap";
const T_PASSTHROUGH: &str = "pt";
pub struct JsCipherText<Leaf, P>(pub CipherText<Leaf, P>);
fn shape_error() -> Error {
Error::new(Status::InvalidArg, "malformed ciphertext value")
}
fn node_to_js<Leaf, P>(env: sys::napi_env, node: CipherText<Leaf, P>) -> Result<sys::napi_value>
where
Leaf: AsRef<[u8]>,
P: ToNapiValue,
{
let raw_env = Env::from_raw(env);
let mut obj = Object::new(&raw_env)?;
match node {
CipherText::Single(leaf) => {
obj.set(T_KEY, T_CIPHERTEXT)?;
obj.set(V_KEY, Buffer::from(leaf.as_ref().to_vec()))?;
}
CipherText::None(leaf) => {
obj.set(T_KEY, T_NONE)?;
obj.set(V_KEY, Buffer::from(leaf.as_ref().to_vec()))?;
}
CipherText::EmptySequence(leaf) => {
obj.set(T_KEY, T_EMPTY_SEQ)?;
obj.set(V_KEY, Buffer::from(leaf.as_ref().to_vec()))?;
}
CipherText::EmptyMap(leaf) => {
obj.set(T_KEY, T_EMPTY_MAP)?;
obj.set(V_KEY, Buffer::from(leaf.as_ref().to_vec()))?;
}
CipherText::Sequence(items) => {
obj.set(T_KEY, T_SEQ)?;
let mut arr = raw_env.create_array(items.len() as u32)?;
for (i, item) in items.into_iter().enumerate() {
arr.set(i as u32, NodeHandle(item))?;
}
obj.set(V_KEY, arr)?;
}
CipherText::Map(entries) => {
obj.set(T_KEY, T_MAP)?;
let map = Object::new(&raw_env)?;
for (key, value) in entries {
if forbidden_key(&key) {
return Err(Error::new(
Status::InvalidArg,
format!("ciphertext map key is not allowed: {key}"),
));
}
let js_value = node_to_js(env, value)?;
define_own_property(&map, &key, js_value)?;
}
obj.set(V_KEY, map)?;
}
CipherText::Passthrough(p) => {
obj.set(T_KEY, T_PASSTHROUGH)?;
obj.set(V_KEY, PassthroughHandle(p))?;
}
}
unsafe { Object::to_napi_value(env, obj) }
}
const T_KEY: &str = "t";
const V_KEY: &str = "v";
struct NodeHandle<Leaf, P>(CipherText<Leaf, P>);
impl<Leaf, P> ToNapiValue for NodeHandle<Leaf, P>
where
Leaf: AsRef<[u8]>,
P: ToNapiValue,
{
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
node_to_js(env, val.0)
}
}
struct PassthroughHandle<P>(P);
impl<P: ToNapiValue> ToNapiValue for PassthroughHandle<P> {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
unsafe { P::to_napi_value(env, val.0) }
}
}
impl<Leaf, P> ToNapiValue for JsCipherText<Leaf, P>
where
Leaf: AsRef<[u8]>,
P: ToNapiValue,
{
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
node_to_js(env, val.0)
}
}
fn node_from_js<Leaf, P>(obj: &Object<'_>, depth: usize) -> Result<CipherText<Leaf, P>>
where
Leaf: From<Vec<u8>>,
P: FromNapiValue,
{
if depth > MAX_DEPTH {
return Err(Error::new(
Status::InvalidArg,
"ciphertext is nested too deeply",
));
}
let t: String = obj.get(T_KEY)?.ok_or_else(shape_error)?;
match t.as_str() {
T_CIPHERTEXT | T_NONE | T_EMPTY_SEQ | T_EMPTY_MAP => {
let buf: Buffer = obj.get(V_KEY)?.ok_or_else(shape_error)?;
let leaf = Leaf::from(buf.to_vec());
Ok(match t.as_str() {
T_CIPHERTEXT => CipherText::Single(leaf),
T_NONE => CipherText::None(leaf),
T_EMPTY_SEQ => CipherText::EmptySequence(leaf),
_ => CipherText::EmptyMap(leaf),
})
}
T_SEQ => {
let arr: Array = obj.get(V_KEY)?.ok_or_else(shape_error)?;
let len = arr.len();
let mut items = Vec::with_capacity(eager_capacity(len));
for i in 0..len {
let element = arr.get::<Unknown>(i)?.ok_or_else(shape_error)?;
if element.get_type()? != ValueType::Object {
return Err(shape_error());
}
let element = Object::from_unknown(element)?;
items.push(node_from_js(&element, depth + 1)?);
}
Ok(CipherText::Sequence(items))
}
T_MAP => {
let map: Object = obj.get(V_KEY)?.ok_or_else(shape_error)?;
let keys = own_enumerable_keys(&map)?;
let mut entries = Vec::with_capacity(keys.len());
for key in keys {
if forbidden_key(&key) {
return Err(Error::new(
Status::InvalidArg,
format!("ciphertext map key is not allowed: {key}"),
));
}
let value: Object = map.get(&key)?.ok_or_else(shape_error)?;
entries.push((key, node_from_js(&value, depth + 1)?));
}
Ok(CipherText::Map(entries))
}
T_PASSTHROUGH => {
let value = get_property_unknown(obj, V_KEY)?;
let p = P::from_unknown(value)?;
Ok(CipherText::Passthrough(p))
}
_ => Err(shape_error()),
}
}
impl<Leaf, P> FromNapiValue for JsCipherText<Leaf, P>
where
Leaf: From<Vec<u8>>,
P: FromNapiValue,
{
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let obj = unsafe { Object::from_napi_value(env, napi_val) }?;
Ok(JsCipherText(node_from_js(&obj, 0)?))
}
}