Skip to content

Commit

Permalink
Merge pull request iotaledger#1 from iotaledger/add_native_library
Browse files Browse the repository at this point in the history
Add native library
  • Loading branch information
lmoe authored Oct 24, 2023
2 parents c503cd5 + e1a076e commit bc0e398
Show file tree
Hide file tree
Showing 10 changed files with 809 additions and 0 deletions.
25 changes: 25 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Created by https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer
# Edit at https://www.toptal.com/developers/gitignore?templates=rust,rust-analyzer

### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

### rust-analyzer ###
# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules)
rust-project.json


# End of https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer
39 changes: 39 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[package]
name = "iota-sdk-native"
version = "0.1.0"
authors = [ "IOTA Stiftung" ]
edition = "2021"
description = "Native wrapper for the IOTA SDK library"
documentation = "https://wiki.iota.org/iota-sdk/welcome"
homepage = "https://www.iota.org/"
repository = "https://github.com/iotaledger/iota-sdk"
license = "Apache-2.0"
keywords = [ "iota", "client", "wallet", "transaction", "native" ]
categories = [ "cryptography::cryptocurrencies" ]
publish = false

[lib]
name = "iota_sdk_native"
crate-type = [ "cdylib" ]
doc = false

[features]
default = ["std"]
std = ["zeroize/std"]

[dependencies]
iota-sdk-bindings-core = { git = "https://github.com/iotaledger/iota-sdk.git", branch = "develop", default-features = false, features = [
"events",
"rocksdb",
"ledger_nano",
"storage",
"stronghold",
"private_key_secret_manager"
] }

futures = { version = "0.3.28", default-features = false }
once_cell = { version = "1.18.0", default-features = false }
serde_json = { version = "1.0.107", default-features = false }
tokio = { version = "1.32.0", default-features = false }
log = { version = "0.4.20", default-features = false }
zeroize = { version = "1.6.0", default-features = false }
5 changes: 5 additions & 0 deletions bindgen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail

cbindgen --crate iota-sdk-native --output headers/iota_sdk.h --lang c
cbindgen --crate iota-sdk-native --output headers/iota_sdk.hpp --lang c++
42 changes: 42 additions & 0 deletions headers/iota_sdk.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

typedef struct Client Client;

typedef struct SecretManager SecretManager;

typedef struct Wallet Wallet;

bool destroy_string(char *ptr);

bool init_logger(const char *config_ptr);

const char *call_utils_method(const char *config_ptr);

const struct Client *create_client(const char *options_ptr);

bool destroy_client(struct Client *client_ptr);

const char *call_client_method(struct Client *client_ptr, char *method_ptr);

const char *binding_get_last_error(void);

const struct SecretManager *create_secret_manager(const char *options_ptr);

bool destroy_secret_manager(struct SecretManager *secret_manager_ptr);

const char *call_secret_manager_method(struct SecretManager *secret_manager, const char *method);

bool destroy_wallet(struct Wallet *wallet_ptr);

const struct Wallet *create_wallet(const char *options_ptr);

const char *call_wallet_method(struct Wallet *wallet_ptr, const char *method_ptr);

bool listen_wallet(struct Wallet *wallet_ptr, const char *events, void (*handler)(const char*));

const struct Client *get_client_from_wallet(struct Wallet *wallet_ptr);

const struct SecretManager *get_secret_manager_from_wallet(struct Wallet *wallet_ptr);
47 changes: 47 additions & 0 deletions headers/iota_sdk.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>

struct Client;

struct SecretManager;

struct Wallet;

extern "C" {

bool destroy_string(char *ptr);

bool init_logger(const char *config_ptr);

const char *call_utils_method(const char *config_ptr);

const Client *create_client(const char *options_ptr);

bool destroy_client(Client *client_ptr);

const char *call_client_method(Client *client_ptr, char *method_ptr);

const char *binding_get_last_error();

const SecretManager *create_secret_manager(const char *options_ptr);

bool destroy_secret_manager(SecretManager *secret_manager_ptr);

const char *call_secret_manager_method(SecretManager *secret_manager, const char *method);

bool destroy_wallet(Wallet *wallet_ptr);

const Wallet *create_wallet(const char *options_ptr);

const char *call_wallet_method(Wallet *wallet_ptr, const char *method_ptr);

bool listen_wallet(Wallet *wallet_ptr, const char *events, void (*handler)(const char*));

const Client *get_client_from_wallet(Wallet *wallet_ptr);

const SecretManager *get_secret_manager_from_wallet(Wallet *wallet_ptr);

} // extern "C"
101 changes: 101 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::{
ffi::{c_char, CStr, CString},
ptr::null,
};

use iota_sdk_bindings_core::{
call_client_method as rust_call_client_method,
iota_sdk::client::{Client as RustClient, ClientBuilder},
ClientMethod,
};

use crate::error::{set_last_error, Error, Result};

pub struct Client {
pub client: RustClient,
}

unsafe fn internal_create_client(options_ptr: *const c_char) -> Result<*const Client> {
let options_string = CStr::from_ptr(options_ptr).to_str().unwrap();
let runtime = tokio::runtime::Runtime::new()?;

let client = runtime.block_on(async move {
if options_string.is_empty() {
return ClientBuilder::new().finish().await;
}

ClientBuilder::new().from_json(options_string)?.finish().await
})?;

let client_wrap = Client { client };
let client_ptr = Box::into_raw(Box::new(client_wrap));

Ok(client_ptr)
}

#[no_mangle]
pub unsafe extern "C" fn create_client(options_ptr: *const c_char) -> *const Client {
match internal_create_client(options_ptr) {
Ok(v) => v,
Err(e) => {
set_last_error(e);
null()
}
}
}

unsafe fn internal_destroy_client(client_ptr: *mut Client) -> Result<()> {
log::debug!("[Rust] Client destroy called");

if client_ptr.is_null() {
log::error!("[Rust] Client pointer was null!");
return Err(Error::from("pointer is null"));
}

let _ = Box::from_raw(client_ptr);

log::debug!("[Rust] Destroyed client");
Ok(())
}

#[no_mangle]
pub unsafe extern "C" fn destroy_client(client_ptr: *mut Client) -> bool {
match internal_destroy_client(client_ptr) {
Ok(_) => true,
Err(e) => {
set_last_error(e);
false
}
}
}

unsafe fn internal_call_client_method(client_ptr: *mut Client, method_ptr: *mut c_char) -> Result<*const c_char> {
let method_str = CStr::from_ptr(method_ptr).to_str().unwrap();

let client = {
assert!(!client_ptr.is_null());
&mut *client_ptr
};

let method = serde_json::from_str::<ClientMethod>(method_str)?;
let response = crate::block_on(async { rust_call_client_method(&client.client, method).await });

let response_string = serde_json::to_string(&response)?;
let s = CString::new(response_string).unwrap();

Ok(s.into_raw())
}

#[no_mangle]
pub unsafe extern "C" fn call_client_method(client_ptr: *mut Client, method_ptr: *mut c_char) -> *const c_char {
match internal_call_client_method(client_ptr, method_ptr) {
Ok(v) => v,
Err(e) => {
set_last_error(e);
null()
}
}
}
94 changes: 94 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use core::convert::{From, Infallible};
use std::{
cell::RefCell,
ffi::{c_char, CString},
ptr,
};

pub(crate) type Result<T> = std::result::Result<T, Error>;

#[derive(Debug)]
pub struct Error {
pub error: String,
}

impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error { error: err.to_string() }
}
}

impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error { error: err.to_string() }
}
}

impl From<iota_sdk_bindings_core::iota_sdk::types::block::Error> for Error {
fn from(err: iota_sdk_bindings_core::iota_sdk::types::block::Error) -> Self {
Error { error: err.to_string() }
}
}

impl From<iota_sdk_bindings_core::Error> for Error {
fn from(err: iota_sdk_bindings_core::Error) -> Self {
Error { error: err.to_string() }
}
}

impl From<iota_sdk_bindings_core::iota_sdk::client::Error> for Error {
fn from(err: iota_sdk_bindings_core::iota_sdk::client::Error) -> Self {
Error { error: err.to_string() }
}
}

impl From<iota_sdk_bindings_core::iota_sdk::wallet::Error> for Error {
fn from(err: iota_sdk_bindings_core::iota_sdk::wallet::Error) -> Self {
Error { error: err.to_string() }
}
}

impl From<Infallible> for Error {
fn from(err: Infallible) -> Self {
Error { error: err.to_string() }
}
}

impl From<&str> for Error {
fn from(err: &str) -> Self {
Self { error: err.to_string() }
}
}

impl From<String> for Error {
fn from(err: String) -> Self {
Self { error: err }
}
}

thread_local! {
#[allow(clippy::box_collection)]
static LAST_ERROR: RefCell<Option<Box<String>>> = RefCell::new(None);
}

pub fn set_last_error(err: Error) {
LAST_ERROR.with(|prev| {
*prev.borrow_mut() = Some(Box::new(err.error));
});
}

#[no_mangle]
pub unsafe extern "C" fn binding_get_last_error() -> *const c_char {
let last_error = LAST_ERROR.with(|prev| prev.borrow_mut().take());

let last_error = match last_error {
Some(err) => err,
None => return ptr::null_mut(),
};

let s = CString::new(last_error.to_string()).unwrap();
s.into_raw()
}
Loading

0 comments on commit bc0e398

Please sign in to comment.