0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-20 12:02:18 +02:00

Use a proc_macro to generate arbitrary FFI/JNI bridging

This commit is contained in:
Jordan Rose 2020-11-23 17:23:34 -08:00
parent fe89cb76de
commit 1c897b232d
14 changed files with 372 additions and 38 deletions

17
Cargo.lock generated
View File

@ -848,11 +848,22 @@ dependencies = [
"futures", "futures",
"jni", "jni",
"libc", "libc",
"libsignal-bridge-macros",
"libsignal-protocol-rust", "libsignal-protocol-rust",
"log", "log",
"paste", "paste",
] ]
[[package]]
name = "libsignal-bridge-macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unzip3",
]
[[package]] [[package]]
name = "libsignal-ffi" name = "libsignal-ffi"
version = "0.1.0" version = "0.1.0"
@ -1751,6 +1762,12 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "unzip3"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99c0ec316ab08201476c032feb2f94a5c8ece5b209765c1fbc4430dd6e931ad6"
[[package]] [[package]]
name = "ureq" name = "ureq"
version = "1.5.2" version = "1.5.2"

View File

@ -111,18 +111,6 @@ pub unsafe extern "C" fn signal_hkdf_derive(
}) })
} }
#[no_mangle]
pub unsafe extern "C" fn signal_address_new(
address: *mut *mut ProtocolAddress,
name: *const c_char,
device_id: c_uint,
) -> *mut SignalFfiError {
run_ffi_safe(|| {
let name = read_c_string(name)?;
box_object(address, Ok(ProtocolAddress::new(name, device_id)))
})
}
ffi_fn_get_uint32!(signal_address_get_device_id(ProtocolAddress) using ffi_fn_get_uint32!(signal_address_get_device_id(ProtocolAddress) using
|obj: &ProtocolAddress| { Ok(obj.device_id()) }); |obj: &ProtocolAddress| { Ok(obj.device_id()) });

View File

@ -35,7 +35,11 @@ cbindgen = subprocess.Popen(['cbindgen'], cwd=os.path.join(our_abs_dir, '..'), s
stdout = str(stdout.decode('utf8')) stdout = str(stdout.decode('utf8'))
stderr = str(stderr.decode('utf8')) stderr = str(stderr.decode('utf8'))
ignore_this_warning = re.compile(r"WARN: Can't find .*\. This usually means that this type was incompatible or not found\.") ignore_this_warning = re.compile(
"("
r"WARN: Can't find .*\. This usually means that this type was incompatible or not found\.|"
r"WARN: Missing `\[defines\]` entry for `feature = \"jni\"` in cbindgen config\."
")")
unknown_warning = False unknown_warning = False

View File

@ -26,21 +26,6 @@ type JavaSignedPreKeyStore = jobject;
type JavaCiphertextMessage = jobject; type JavaCiphertextMessage = jobject;
type JavaSenderKeyStore = jobject; type JavaSenderKeyStore = jobject;
#[no_mangle]
pub unsafe extern "C" fn Java_org_signal_client_internal_Native_ProtocolAddress_1New(
env: JNIEnv,
_class: JClass,
name: JString,
device_id: jint,
) -> ObjectHandle {
run_ffi_safe(&env, || {
let name: String = env.get_string(name)?.into();
let device_id = jint_to_u32(device_id)?;
let address = ProtocolAddress::new(name, device_id);
box_object::<ProtocolAddress>(Ok(address))
})
}
jni_fn_get_jint!(Java_org_signal_client_internal_Native_ProtocolAddress_1DeviceId(ProtocolAddress) using jni_fn_get_jint!(Java_org_signal_client_internal_Native_ProtocolAddress_1DeviceId(ProtocolAddress) using
|obj: &ProtocolAddress| { Ok(obj.device_id()) }); |obj: &ProtocolAddress| { Ok(obj.device_id()) });

View File

@ -21,13 +21,6 @@ pub unsafe fn native_handle_cast_optional<T>(
Ok(Some(&mut *(handle as *mut T))) Ok(Some(&mut *(handle as *mut T)))
} }
pub fn jint_to_u32(v: jint) -> Result<u32, SignalJniError> {
if v < 0 {
return Err(SignalJniError::IntegerOverflow(format!("{} to u32", v)));
}
Ok(v as u32)
}
pub fn jlong_to_u64(v: jlong) -> Result<u64, SignalJniError> { pub fn jlong_to_u64(v: jlong) -> Result<u64, SignalJniError> {
if v < 0 { if v < 0 {
return Err(SignalJniError::IntegerOverflow(format!("{} to u64", v))); return Err(SignalJniError::IntegerOverflow(format!("{} to u64", v)));

View File

@ -13,6 +13,7 @@ license = "AGPL-3.0-only"
[dependencies] [dependencies]
libsignal-protocol-rust = { path = "../../protocol" } libsignal-protocol-rust = { path = "../../protocol" }
aes-gcm-siv = { path = "../../aes-gcm-siv" } aes-gcm-siv = { path = "../../aes-gcm-siv" }
libsignal-bridge-macros = { path = "macros" }
futures = "0.3.7" futures = "0.3.7"
log = "0.4.11" log = "0.4.11"
paste = "1.0" paste = "1.0"

View File

@ -0,0 +1,20 @@
#
# Copyright (C) 2020 Signal Messenger, LLC.
# SPDX-License-Identifier: AGPL-3.0-only
#
[package]
name = "libsignal-bridge-macros"
version = "0.1.0"
authors = ["Jack Lloyd <jack@signal.org>", "Jordan Rose <jrose@signal.org>"]
edition = "2018"
license = "AGPL-3.0-only"
[lib]
proc-macro = true
[dependencies]
syn = { version = "1.0", features = ["full"] }
proc-macro2 = "1.0"
quote = "1.0"
unzip3 = "1.0.0"

View File

@ -0,0 +1,139 @@
//
// Copyright 2020 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
#![feature(box_patterns)]
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::*;
use syn::*;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use unzip3::Unzip3;
fn ffi_bridge_fn(name: String, sig: &Signature) -> TokenStream2 {
let name = format_ident!("signal_{}", name);
let (output_args, output_processing) = match sig.output {
ReturnType::Default => (quote!(), quote!()),
ReturnType::Type(_, ref ty) => (
quote!(out: *mut ffi_result_type!(#ty),), // note the trailing comma
quote!(<#ty as ffi::ResultTypeInfo>::write_to(out, __result)?)
)
};
let (input_names, input_args, input_processing): (Vec<Ident>, Vec<TokenStream2>, Vec<TokenStream2>) = sig.inputs.iter().map(|arg| match arg {
FnArg::Receiver(tokens) => (
Ident::new("self", tokens.self_token.span),
Error::new(tokens.self_token.span, "cannot have 'self' parameter").to_compile_error(),
quote!()
),
FnArg::Typed(PatType { attrs, pat: box Pat::Ident(name), colon_token, ty: ty @ box Type::Reference(TypeReference { elem: box Type::Array(_), .. }) }) => {
let size_arg = format_ident!("{}_size", name.ident);
(
name.ident.clone(),
quote!(#(#attrs)* #name #colon_token ffi_arg_type!(#ty), #size_arg: usize),
quote!(let #name = <#ty as ffi::SizedArgTypeInfo>::convert_from(#name, #size_arg)?),
)
}
FnArg::Typed(PatType { attrs, pat: box Pat::Ident(name), colon_token, ty }) => (
name.ident.clone(),
quote!(#(#attrs)* #name #colon_token ffi_arg_type!(#ty)),
quote!(let #name = <#ty as ffi::ArgTypeInfo>::convert_from(#name)?),
),
FnArg::Typed(PatType { pat, .. }) => (
Ident::new("unexpected", pat.span()),
Error::new(pat.span(), "cannot use patterns in paramater").to_compile_error(),
quote!()
)
}).unzip3();
let orig_name = sig.ident.clone();
quote! {
#[cfg(feature = "ffi")]
#[no_mangle]
pub unsafe extern "C" fn #name(
#output_args
#(#input_args),*
) -> *mut ffi::SignalFfiError {
ffi::run_ffi_safe(|| {
#(#input_processing);*;
let __result = #orig_name(#(#input_names),*);
#output_processing;
Ok(())
})
}
}
}
fn jni_bridge_fn(name: String, sig: &Signature) -> TokenStream2 {
let name = format_ident!("Java_org_signal_client_internal_Native_{}", name);
let output = match sig.output {
ReturnType::Default => quote!(),
ReturnType::Type(_, ref ty) => quote!(-> jni_result_type!(#ty)),
};
let (input_names, input_args, input_processing): (Vec<Ident>, Vec<TokenStream2>, Vec<TokenStream2>) = sig.inputs.iter().map(|arg| match arg {
FnArg::Receiver(tokens) => (
Ident::new("self", tokens.self_token.span),
Error::new(tokens.self_token.span, "cannot have 'self' parameter").to_compile_error(),
quote!()
),
FnArg::Typed(PatType { attrs, pat: box Pat::Ident(name), colon_token, ty }) => (
name.ident.clone(),
quote!(#(#attrs)* #name #colon_token jni_arg_type!(#ty)),
quote!(let #name = <#ty as jni::ArgTypeInfo>::convert_from(&env, #name)?),
),
FnArg::Typed(PatType { pat, .. }) => (
Ident::new("unexpected", pat.span()),
Error::new(pat.span(), "cannot use patterns in paramater").to_compile_error(),
quote!()
)
}).unzip3();
let orig_name = sig.ident.clone();
quote! {
#[cfg(feature = "jni")]
#[no_mangle]
pub unsafe extern "C" fn #name(
env: jni::JNIEnv,
_class: jni::JClass,
#(#input_args),*
) #output {
jni::run_ffi_safe(&env, || {
#(#input_processing);*;
jni::ResultTypeInfo::convert_into(#orig_name(#(#input_names),*), &env)
})
}
}
}
#[proc_macro_attribute]
pub fn bridge_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
let function = parse_macro_input!(item as ItemFn);
let item_names = parse_macro_input!(attr with Punctuated<MetaNameValue, Token![,]>::parse_terminated);
let ffi_name = match item_names.iter().find(|meta| meta.path.get_ident().map_or(false, |ident| ident == "ffi")) {
Some(MetaNameValue { lit: Lit::Str(name_str), .. }) => name_str.value(),
Some(meta) => return Error::new(meta.lit.span(), "ffi name must be a string literal").to_compile_error().into(),
None => function.sig.ident.to_string(),
};
let jni_name = match item_names.iter().find(|meta| meta.path.get_ident().map_or(false, |ident| ident == "jni")) {
Some(MetaNameValue { lit: Lit::Str(name_str), .. }) => name_str.value(),
Some(meta) => return Error::new(meta.lit.span(), "jni name must be a string literal").to_compile_error().into(),
None => function.sig.ident.to_string(),
};
let ffi_fn = ffi_bridge_fn(ffi_name, &function.sig);
let jni_fn = jni_bridge_fn(jni_name, &function.sig);
let mut result = function.into_token_stream();
result.extend(ffi_fn);
result.extend(jni_fn);
result.into()
}

View File

@ -0,0 +1,95 @@
//
// Copyright 2020 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
use libc::{c_char, c_uchar};
use libsignal_protocol_rust::*;
use std::ffi::CStr;
use crate::ffi::error::*;
pub(crate) trait ArgTypeInfo: Sized {
type ArgType;
fn convert_from(foreign: Self::ArgType) -> Result<Self, SignalFfiError>;
}
pub(crate) trait SizedArgTypeInfo: Sized {
type ArgType;
fn convert_from(foreign: Self::ArgType, size: usize) -> Result<Self, SignalFfiError>;
}
pub(crate) trait ResultTypeInfo: Sized {
type ResultType;
fn convert_into(self) -> Result<Self::ResultType, SignalFfiError>;
fn write_to(ptr: *mut Self::ResultType, value: Self) -> Result<(), SignalFfiError> {
if ptr.is_null() {
return Err(SignalFfiError::NullPointer);
}
unsafe { *ptr = value.convert_into()? };
Ok(())
}
}
pub(crate) trait TrivialTypeInfo {}
impl<T: TrivialTypeInfo> ArgTypeInfo for T {
type ArgType = Self;
fn convert_from(foreign: Self) -> Result<Self, SignalFfiError> { Ok(foreign) }
}
impl<T: TrivialTypeInfo> ResultTypeInfo for T {
type ResultType = Self;
fn convert_into(self) -> Result<Self, SignalFfiError> { Ok(self) }
}
impl SizedArgTypeInfo for &[u8] {
type ArgType = *const c_uchar;
fn convert_from(input: Self::ArgType, input_len: usize) -> Result<Self, SignalFfiError> {
if input.is_null() {
if input_len != 0 {
return Err(SignalFfiError::NullPointer);
}
// We can't just fall through because slice::from_raw_parts still expects a non-null pointer. Reference a dummy buffer instead.
return Ok(&[]);
}
unsafe { Ok(std::slice::from_raw_parts(input, input_len)) }
}
}
impl ArgTypeInfo for String {
type ArgType = *const c_char;
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn convert_from(foreign: *const c_char) -> Result<Self, SignalFfiError> {
if foreign.is_null() {
return Err(SignalFfiError::NullPointer);
}
match unsafe { CStr::from_ptr(foreign).to_str() } {
Ok(s) => Ok(s.to_owned()),
Err(_) => Err(SignalFfiError::InvalidUtf8String),
}
}
}
impl TrivialTypeInfo for u32 {}
impl TrivialTypeInfo for usize {}
impl ResultTypeInfo for ProtocolAddress {
type ResultType = *mut ProtocolAddress;
fn convert_into(self) -> Result<Self::ResultType, SignalFfiError> {
Ok(Box::into_raw(Box::new(self)))
}
}
macro_rules! ffi_arg_type {
(u32) => (u32);
(usize) => (libc::size_t);
(&[u8]) => (*const libc::c_uchar);
(String) => (*const libc::c_char);
}
macro_rules! ffi_result_type {
( $typ:ty ) => (*mut $typ);
}

View File

@ -7,6 +7,10 @@ use libc::{c_char, c_uchar, size_t};
use libsignal_protocol_rust::*; use libsignal_protocol_rust::*;
use std::ffi::CString; use std::ffi::CString;
#[macro_use]
mod convert;
pub(crate) use convert::*;
mod error; mod error;
pub use error::*; pub use error::*;

View File

@ -0,0 +1,62 @@
//
// Copyright 2020 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
use jni::JNIEnv;
use jni::objects::JString;
use libsignal_protocol_rust::*;
use crate::jni::*;
pub(crate) trait ArgTypeInfo<'a>: Sized {
type ArgType;
fn convert_from(env: &JNIEnv<'a>, foreign: Self::ArgType) -> Result<Self, SignalJniError>;
}
pub(crate) trait ResultTypeInfo<'a>: Sized {
type ResultType;
fn convert_into(self, env: &JNIEnv<'a>) -> Result<Self::ResultType, SignalJniError>;
}
pub(crate) trait TrivialTypeInfo {}
impl<'a, T: TrivialTypeInfo> ArgTypeInfo<'a> for T {
type ArgType = Self;
fn convert_from(_env: &JNIEnv<'a>, foreign: Self) -> Result<Self, SignalJniError> { Ok(foreign) }
}
impl<'a, T: TrivialTypeInfo> ResultTypeInfo<'a> for T {
type ResultType = Self;
fn convert_into(self, _env: &JNIEnv<'a>) -> Result<Self, SignalJniError> { Ok(self) }
}
impl<'a> ArgTypeInfo<'a> for u32 {
type ArgType = jint;
fn convert_from(_env: &JNIEnv<'a>, foreign: jint) -> Result<Self, SignalJniError> {
jint_to_u32(foreign)
}
}
impl<'a> ArgTypeInfo<'a> for String {
type ArgType = JString<'a>;
fn convert_from(env: &JNIEnv<'a>, foreign: JString<'a>) -> Result<Self, SignalJniError> {
Ok(env.get_string(foreign)?.into())
}
}
impl<'a> ResultTypeInfo<'a> for ProtocolAddress {
type ResultType = ObjectHandle;
fn convert_into(self, _env: &JNIEnv<'a>) -> Result<Self::ResultType, SignalJniError> {
box_object(Ok(self))
}
}
macro_rules! jni_arg_type {
(u32) => (jni::jint);
(String) => (jni::JString);
}
macro_rules! jni_result_type {
( $typ:ty ) => (jni::ObjectHandle);
}

View File

@ -4,16 +4,20 @@
// //
use jni::objects::{JObject, JValue}; use jni::objects::{JObject, JValue};
use jni::sys::{_jobject, jboolean, jint, jlong}; use jni::sys::{_jobject, jboolean, jlong};
use aes_gcm_siv::Error as AesGcmSivError; use aes_gcm_siv::Error as AesGcmSivError;
use libsignal_protocol_rust::*; use libsignal_protocol_rust::*;
pub(crate) use jni::objects::JClass; pub(crate) use jni::objects::{JClass, JString};
pub(crate) use jni::strings::JNIString; pub(crate) use jni::strings::JNIString;
pub(crate) use jni::sys::{jbyteArray, jstring}; pub(crate) use jni::sys::{jbyteArray, jint, jstring};
pub(crate) use jni::JNIEnv; pub(crate) use jni::JNIEnv;
#[macro_use]
mod convert;
pub(crate) use convert::*;
mod error; mod error;
pub use error::*; pub use error::*;
@ -200,6 +204,13 @@ pub unsafe fn native_handle_cast<T>(
Ok(&mut *(handle as *mut T)) Ok(&mut *(handle as *mut T))
} }
pub fn jint_to_u32(v: jint) -> Result<u32, SignalJniError> {
if v < 0 {
return Err(SignalJniError::IntegerOverflow(format!("{} to u32", v)));
}
Ok(v as u32)
}
pub fn to_jbytearray<T: AsRef<[u8]>>( pub fn to_jbytearray<T: AsRef<[u8]>>(
env: &JNIEnv, env: &JNIEnv,
data: Result<T, SignalProtocolError>, data: Result<T, SignalProtocolError>,

View File

@ -6,6 +6,7 @@
#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_safety_doc)]
use aes_gcm_siv::Aes256GcmSiv; use aes_gcm_siv::Aes256GcmSiv;
use libsignal_bridge_macros::*;
use libsignal_protocol_rust::*; use libsignal_protocol_rust::*;
use std::convert::TryFrom; use std::convert::TryFrom;
@ -29,6 +30,11 @@ bridge_get_string!(name(ProtocolAddress), ffi = address_get_name =>
|p| Ok(p.name()) |p| Ok(p.name())
); );
#[bridge_fn(jni = "ProtocolAddress_1New")]
fn address_new(name: String, device_id: u32) -> ProtocolAddress {
ProtocolAddress::new(name, device_id)
}
bridge_destroy!(PublicKey, ffi = publickey, jni = ECPublicKey); bridge_destroy!(PublicKey, ffi = publickey, jni = ECPublicKey);
bridge_deserialize!(PublicKey::deserialize, ffi = publickey, jni = None); bridge_deserialize!(PublicKey::deserialize, ffi = publickey, jni = None);
bridge_get_bytearray!(serialize(PublicKey), ffi = publickey_serialize, jni = ECPublicKey_1Serialize => bridge_get_bytearray!(serialize(PublicKey), ffi = publickey_serialize, jni = ECPublicKey_1Serialize =>

View File

@ -33,6 +33,15 @@ macro_rules! expr_as_fn {
}; };
} }
// macro_rules! bridge_fn {
// (ffi = $ffi_name:ident, jni = $jni_name:ident, $($body:tt)+) => {
// #[cfg(feature = "ffi")]
// ffi_bridge_fn!($ffi_name $($body)+);
// // #[cfg(feature = "jni")]
// // jni_bridge_get_string!($name($typ) $(as $jni_name)? => $body);
// }
// }
macro_rules! bridge_destroy { macro_rules! bridge_destroy {
($typ:ty $(, ffi = $ffi_name:ident)? $(, jni = $jni_name:ident)? ) => { ($typ:ty $(, ffi = $ffi_name:ident)? $(, jni = $jni_name:ident)? ) => {
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]