use std::collections::HashMap;
use std::time::Duration;
use prost::Message as _;
use crate::ffi::error::{
ffi_error, success, DeRecError, DEREC_CODE_FFI_BAD_PROTO, DEREC_CODE_FFI_INVALID_ENUM,
DEREC_CODE_FFI_NULL_PTR,
};
use crate::ffi::protocol::stores::{
ChannelStoreCallbacks, DotnetChannelStore, DotnetSecretStore, DotnetShareStore,
DotnetStateStore, DotnetTransport, DotnetUserSecretStore, SecretStoreCallbacks,
ShareStoreCallbacks, StateStoreCallbacks, TransportCallbacks, UserSecretStoreCallbacks,
};
use crate::protocol::DeRecProtocolBuilder;
mod config;
mod flow;
mod pairing;
pub(super) type Protocol = crate::protocol::DeRecProtocol<
DotnetChannelStore,
DotnetShareStore,
DotnetSecretStore,
DotnetUserSecretStore,
DotnetStateStore,
DotnetTransport,
>;
pub struct DeRecProtocolHandle {
pub(super) runtime: tokio::runtime::Runtime,
pub(super) inner: std::sync::Mutex<Protocol>,
}
impl DeRecProtocolHandle {
pub(super) fn lock_inner(&self) -> std::sync::MutexGuard<'_, Protocol> {
self.inner
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
pub(super) fn validate_transport(
uri: &str,
protocol: i32,
) -> Result<crate::transport::TransportProtocol, DeRecError> {
let proto = derec_proto::TransportProtocol {
uri: uri.to_owned(),
protocol,
};
crate::transport::TransportProtocol::try_from(&proto)
.map_err(|e| crate::ffi::error::from_lib_error(crate::Error::Transport(e)))
}
#[repr(C)]
pub struct DeRecProtocolNewResult {
pub error: DeRecError,
pub handle: *mut DeRecProtocolHandle,
}
#[repr(C)]
pub struct DeRecAutoAcceptPolicy {
pub pairing: u32,
pub pre_pair: u32,
pub store_share: u32,
pub verify_share: u32,
pub discovery: u32,
pub get_share: u32,
pub unpair: u32,
pub update_channel_info: u32,
}
impl From<DeRecAutoAcceptPolicy> for crate::protocol::AutoAcceptPolicy {
fn from(p: DeRecAutoAcceptPolicy) -> Self {
Self {
pairing: p.pairing != 0,
pre_pair: p.pre_pair != 0,
store_share: p.store_share != 0,
verify_share: p.verify_share != 0,
discovery: p.discovery != 0,
get_share: p.get_share != 0,
unpair: p.unpair != 0,
update_channel_info: p.update_channel_info != 0,
}
}
}
impl From<DeRecError> for DeRecProtocolNewResult {
fn from(error: DeRecError) -> Self {
Self {
error,
handle: std::ptr::null_mut(),
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn derec_protocol_new(
secret_id: u64,
channel_store_cb: *const ChannelStoreCallbacks,
secret_store_cb: *const SecretStoreCallbacks,
share_store_cb: *const ShareStoreCallbacks,
user_secret_store_cb: *const UserSecretStoreCallbacks,
state_store_cb: *const StateStoreCallbacks,
transport_cb: *const TransportCallbacks,
own_transport_uri_ptr: *const u8,
own_transport_uri_len: usize,
own_transport_protocol: i32,
threshold: u32,
keep_versions_count: u32,
communication_info_ptr: *const u8,
communication_info_len: usize,
timeout_in_secs: u32,
auto_respond_on_failure: u32,
unpair_ack: i32,
auto_reply_to: u32,
auto_accept: DeRecAutoAcceptPolicy,
has_replica_id: u32,
replica_id: u64,
) -> DeRecProtocolNewResult {
if channel_store_cb.is_null()
|| secret_store_cb.is_null()
|| share_store_cb.is_null()
|| user_secret_store_cb.is_null()
|| state_store_cb.is_null()
|| transport_cb.is_null()
{
return ffi_error(
DEREC_CODE_FFI_NULL_PTR,
"store/transport callback pointer is null",
)
.into();
}
let own_uri = if own_transport_uri_len == 0 {
String::new()
} else if own_transport_uri_ptr.is_null() {
return ffi_error(
DEREC_CODE_FFI_NULL_PTR,
"own_transport_uri_ptr is null but length is non-zero",
)
.into();
} else {
let bytes =
unsafe { std::slice::from_raw_parts(own_transport_uri_ptr, own_transport_uri_len) };
match std::str::from_utf8(bytes) {
Ok(s) => s.to_owned(),
Err(_) => {
return ffi_error(
DEREC_CODE_FFI_BAD_PROTO,
"own_transport_uri is not valid UTF-8",
)
.into();
}
}
};
let own_transport: crate::transport::TransportProtocol = if own_uri.is_empty() {
crate::transport::TransportProtocol::new(
String::new(),
derec_proto::Protocol::Https,
)
} else {
match validate_transport(&own_uri, own_transport_protocol) {
Ok(tp) => tp,
Err(e) => return e.into(),
}
};
let info: HashMap<String, String> = if communication_info_len == 0 {
HashMap::new()
} else {
if communication_info_ptr.is_null() {
return ffi_error(
DEREC_CODE_FFI_NULL_PTR,
"communication_info_ptr is null but length is non-zero",
)
.into();
}
let bytes =
unsafe { std::slice::from_raw_parts(communication_info_ptr, communication_info_len) };
match derec_proto::CommunicationInfo::decode(bytes) {
Ok(c) => c
.communication_info_entries
.into_iter()
.filter_map(|e| {
let s = match e.value? {
derec_proto::communication_info_key_value::Value::StringValue(s) => s,
derec_proto::communication_info_key_value::Value::BytesValue(_) => {
return None;
}
};
Some((e.key, s))
})
.collect(),
Err(_) => {
return ffi_error(
DEREC_CODE_FFI_BAD_PROTO,
"communication_info is not a valid CommunicationInfo proto",
)
.into();
}
}
};
let unpair_ack_value = match unpair_ack {
0 => crate::protocol::UnpairAck::Required,
1 => crate::protocol::UnpairAck::NotRequired,
other => {
return ffi_error(
DEREC_CODE_FFI_INVALID_ENUM,
format!("invalid unpair_ack: {other}"),
)
.into();
}
};
let channel_store = DotnetChannelStore {
cb: unsafe { std::ptr::read(channel_store_cb) },
};
let secret_store = DotnetSecretStore {
cb: unsafe { std::ptr::read(secret_store_cb) },
};
let share_store = DotnetShareStore {
cb: unsafe { std::ptr::read(share_store_cb) },
};
let user_secret_store = DotnetUserSecretStore {
cb: unsafe { std::ptr::read(user_secret_store_cb) },
};
let state_store = DotnetStateStore {
cb: unsafe { std::ptr::read(state_store_cb) },
};
let transport = DotnetTransport {
cb: unsafe { std::ptr::read(transport_cb) },
};
let mut builder = DeRecProtocolBuilder::new(secret_id)
.with_channel_store(channel_store)
.with_share_store(share_store)
.with_secret_store(secret_store)
.with_user_secret_store(user_secret_store)
.with_state_store(state_store)
.with_transport(transport)
.with_own_transport(own_transport)
.with_threshold(threshold as usize)
.with_keep_versions_count(keep_versions_count as usize)
.with_communication_info(info)
.with_timeout(Duration::from_secs(u64::from(timeout_in_secs.max(1))))
.with_auto_respond_on_failure(auto_respond_on_failure != 0)
.with_unpair_ack(unpair_ack_value)
.with_auto_reply_to(auto_reply_to != 0)
.with_auto_accept(auto_accept.into());
if has_replica_id != 0 {
builder = builder.with_replica_id(replica_id);
}
let inner = match builder.build() {
Ok(p) => p,
Err(e) => return crate::ffi::error::from_lib_error(e).into(),
};
let runtime = match tokio::runtime::Builder::new_current_thread()
.build() {
Ok(rt) => rt,
Err(e) => {
return ffi_error(
DEREC_CODE_FFI_BAD_PROTO,
format!("failed to build tokio runtime: {e}"),
)
.into();
}
};
let handle = Box::new(DeRecProtocolHandle {
runtime,
inner: std::sync::Mutex::new(inner),
});
DeRecProtocolNewResult {
error: success(),
handle: Box::into_raw(handle),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn derec_protocol_free(handle: *mut DeRecProtocolHandle) {
if handle.is_null() {
return;
}
unsafe {
drop(Box::from_raw(handle));
}
}