Expose Rust function through FFI with c bindings - rust

I'm creating a Rust library and want to expose my Rust functions through c bindings to Dart. This question is just related to the setup of actually exposing the Rust function through C bindings and not on how to call it in Dart.
This is my function which I want to expose through FFI:
pub fn create_channel(credential: String) -> Result<String, iota_streams::core::Error> {
let seed = create_seed::new();
// Create the Transport Client
let client = Client::new_from_url(&dotenv::var("URL").unwrap());
let mut author = Author::new(&seed, ChannelType::SingleBranch, client.clone());
// Create the channel with an announcement message. Make sure to save the resulting link somewhere,
let announcement_link = author.send_announce()?;
// This link acts as a root for the channel itself
let ann_link_string = announcement_link.to_string();
// Author will now send signed encrypted messages in a chain
let msg_inputs = vec![credential];
let mut prev_msg_link = announcement_link;
for input in &msg_inputs {
let (msg_link, _seq_link) = author.send_signed_packet(
&prev_msg_link,
&Bytes::default(),
&Bytes(input.as_bytes().to_vec()),
)?;
println!("Sent msg: {}", msg_link);
prev_msg_link = msg_link;
}
Ok(ann_link_string)
}
The credential String is supposed to be just a stringified json object. Which I want to provide from Dart through C bindings into Rust and then use inside my create_channel function. But I don't know how to define the type of my credential parameter, because it would come in as a C type and then would need to be converted to Rust.
#[no_mangle]
pub extern "C" fn create_channel(credential: *const raw::c_char) -> String {
streams::create_channel(credential).unwrap()
}
Right now I'm just defining the parameters of my extern function to be off type c_char but I would then need to convert this C type to a Rust String or &str. So that I can use it inside of my actual create_channel function written in Rust.
As what types should I define the credential parameter and how would I convert the c_char into a String or &str?

Rust has the convenient CStr and CString types, you can use Cstr::from_ptr to wrap the raw string and then call to_str on that. Of course you need to do some error handling here for cases where the string isn't valid UTF-8.

Related

Is it possible to deserialize a raw string directly into a specific type?

I've searched a bit and can't seem to find an answer for this, so this is probably a duplicate.
I have a String like this (the actual strings are not literals):
let param_type_raw = String::from("address");
I want to deserialize it into the type ethabi::ParamType, which implements the Deserializer trait. So far, I've came up with this:
let param_type: ParamType = serde_json::from_str(format!("\"{}\"", param_type_raw).as_str())?;
However, it seems extremely redundant to convert the string into a JSON string just to deserialize it into ParamType. I'm certain there must be a better way of just deserializing a String by itself.
There is a specific function for that:
use ethabi::{param_type::Reader, ParamType};
fn main() {
let param_type_raw = String::from("address");
let param_type: ParamType = Reader::read(&param_type_raw).unwrap();
println!("{:?}", param_type);
}
Address
Answering the title more generally: There is a suite of deserializers available for each of the primitive types in the serde::de::value module that can be created with .into_deserializer() provided by the IntoDeserializer trait.
So if you have a string and you know your type can be deserialized from a string, you can do this:
use ethabi::ParamType;
use serde::de::value::Error;
use serde::de::{Deserialize, IntoDeserializer};
fn main() {
let param_type_raw = "address";
let param_type: Result<_, Error> = ParamType::deserialize(param_type_raw.into_deserializer());
println!("{:?}", param_type);
}
Ok(Address)
Slightly annoying since the deserializers are generic over the error type, so you have to specify one somewhere, but that's what serde::de::value::Error is for.

How do I insert a dynamic byte string into a vector?

I need to create packet to send to the server. For this purpose I use vector with byteorder crate. When I try to append string, Rust compiler tells I use unsafe function and give me an error.
use byteorder::{LittleEndian, WriteBytesExt};
fn main () {
let login = "test";
let packet_length = 30 + (login.len() as i16);
let mut packet = Vec::new();
packet.write_u8(0x00);
packet.write_i16::<LittleEndian>(packet_length);
packet.append(&mut Vec::from(String::from("game name ").as_bytes_mut()));
// ... rest code
}
The error is:
packet.append(&mut Vec::from(String::from("game name ").as_bytes_mut()));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
This is playground to reproduce: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=381c6d14660d47beaece15d068b3dc6a
What is the correct way to insert some string as bytes into vector ?
The unsafe function called was as_bytes_mut(). This creates a mutable reference with exclusive access to the bytes representing the string, allowing you to modify them. You do not really need a mutable reference in this case, as_bytes() would have sufficed.
However, there is a more idiomatic way. Vec<u8> also functions as a writer (it implements std::io::Write), so you can use one of its methods, or even the write! macro, to write encoded text on it.
use std::io::Write;
use byteorder::{LittleEndian, WriteBytesExt};
fn main () -> Result<(), std::io::Error> {
let login = "test";
let packet_length = 30 + (login.len() as i16);
let mut packet = Vec::new();
packet.write_u8(0x00)?;
packet.write_i16::<LittleEndian>(packet_length)?;
let game_name = String::from("game name");
write!(packet, "{} ", game_name)?;
Ok(())
}
Playground
See also:
Use write! macro with a string instead of a string literal
What's the de-facto way of reading and writing files in Rust 1.x?
You can use .extend() on the Vec and pass in the bytes representation of the String:
use byteorder::{LittleEndian, WriteBytesExt};
fn main() {
let login = "test";
let packet_length = 30 + (login.len() as i16);
let mut packet = Vec::new();
packet.write_u8(0x00);
packet.write_i16::<LittleEndian>(packet_length);
let string = String::from("game name ");
packet.extend(string.as_bytes());
}
Playground

Pass a pointer string as an argument in host import function in Rust WebAassembly (wasmtime)

so I have the following import in my generated .wast file (disclaimer: I have not written the wasm file myself):
(import "index" "bigDecimal.fromString" (func $fimport$1 (param i32) (result i32)))
and I need to write up the host import function in Rust. I cannot use &str in Rust but also the import requires an i32. I'm guessing I need to pass a pointer to a string, defined in Rust? Can anyone point me to the right direction? Some example of that being done in wasmtime?
let from_string = Func::wrap(&store, |a: i32| {
println!("a={}", a);
});
Thanks a ton in advance!
You are right, the parameter a is a pointer to the string inside of the Wasm modules memory. You can access this via wasmtime::Memory::data_ptr(...) function. To get the memory from the caller you can add a parameter of type wasmtime::Caller to your closure. This parameter must not be in your wasm modules function signature but only in the host functions signature.
I hope this short example will help:
let read_wasm_string_func = Func::wrap(&store, |mut caller: Caller<'_, WasiCtx>, ptr_wasm: i32| -> i32 {
let memory = caller.get_export("memory").unwrap().into_memory().unwrap();
unsafe {
let ptr_native = memory.data_ptr(&caller).offset(ptr_wasm as isize);
// Do something with the pointer to turn it into a number
// return int
return num;
}
});

Is it possible to build a HashMap of &str referencing environment variables?

I'm trying to make a read-only map of environment variables.
fn os_env_hashmap() -> HashMap<&'static str, &'static str> {
let mut map = HashMap::new();
use std::env;
for (key,val) in env::vars_os() {
let k = key.to_str();
if k.is_none() { continue }
let v = val.to_str();
if v.is_none() { continue }
k.unwrap();
//map.insert( k.unwrap(), v.unwrap() );
}
return map;
}
Can't seem to uncomment the "insert" line near the bottom without compiler errors about key,val,k, and v being local.
I might be able to fix the compiler error by using String instead of str, but str seems perfect for a read-only result.
Feel free to suggest a more idiomatic way to do this.
This is unfortunately not straightforward using only the facilities of the Rust standard library.
If env::vars_os() returned an iterator over &'static OsStr instead of OsString, this would be trivial. Unfortunately, not all platforms allow creating an &OsStr to the contents of an environment variable. In particular, on Windows, the native encoding is UTF-16 but the encoding needed by OsStr is WTF-8. For this reason, there really is no OsStr anywhere you could take a reference to, until you create an OsString by iterating over env::vars_os().
The simplest thing, as the question comments mention, is to return owned Strings:
fn os_env_hashmap() -> HashMap<String, String> {
let mut map = HashMap::new();
use std::env;
for (key, val) in env::vars_os() {
// Use pattern bindings instead of testing .is_some() followed by .unwrap()
if let (Ok(k), Ok(v)) = (key.into_string(), val.into_string()) {
map.insert(k, v);
}
}
return map;
}
The result is not "read-only", but it is not shared, so you cannot cause data races or other weird bugs by mutating it.
See also
Is there any way to return a reference to a variable created in a function?
Return local String as a slice (&str)

Map C-like packed data structure to Rust struct

I'm fairly new to Rust and have spent most of my time writing code in C/C++. I have a flask webserver that returns back a packed data structure in the form of length + null-terminated string:
test_data = "Hello there bob!" + "\x00"
test_data = test_data.encode("utf-8")
data = struct.pack("<I", len(test_data ))
data += test_data
return data
In my rust code, I'm using the easy_http_request crate and can successfully get the response back by calling get_from_url_str. What I'm trying to do is map the returned response back to the Test data structure (if possible). I've attempted to use align_to to unsuccessfully get the string data mapped to the structure.
extern crate easy_http_request;
extern crate libc;
use easy_http_request::DefaultHttpRequest;
use libc::c_char;
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
struct Test {
a: u32,
b: *const c_char // TODO: What do I put here???
}
fn main() {
let response = DefaultHttpRequest::get_from_url_str("http://localhost:5000/").unwrap().send().unwrap();
let (head, body, _tail) = unsafe { response.body.align_to::<Test>() };
let my_test: Test = body[0];
println!("{}", my_test.a); // Correctly prints '17'
println!("{:?}", my_test.b); // Fails
}
I'm not sure this is possible in Rust. In the response.body I can correctly see the null-terminated string, so I know the data is there. Just unsure if there's a way to map it to a string in the Test structure. There's no reason I need to use a null-terminated string. Ultimately, I'm just trying to map a data structure of size and a string to a Rust struct of the similar types.
It looks like you are confused by two different meanings of pack:
* In Python, pack is a protocol of sorts to serialize data into an array of bytes.
* In Rust, pack is a directive added to a struct to remove padding between members and disable other weirdness.
While they can be use together to make a protocol work, that is not the case, because in your pack you have a variable-length member. And trying to serialize/deserialize a pointer value directly is a very bad idea.
Your packed flask message is basically:
4 bytes litte endian value with the number of bytes in the string.
so many bytes indicated above for the string, encoded in utf-8.
For that you do not need a packed struct. The easiest way is to just read the fields manually, one by one. Something like this (error checking omitted):
use std::convert::TryInto;
let a = i32::from_le_bytes(response[0..4].try_into().unwrap());
let b = std::str::from_utf8(&response[4 .. 4 + a as usize]).unwrap();
Don't use raw pointers, they are unsafe to use and recommended only when there are strong reasons to
get around Rust’s safety guarantees.
At minumum a struct that fits your requirement is something like:
struct Test<'a> {
value: &'a str
}
or a String owned value for avoiding lifetime dependencies.
A reference to &str comprises a len and a pointer (it is not a C-like char * pointer).
By the way, the hard part is not the parsing of the wire protocol but to manage correctly all the possible
decoding errors and avoid unexpected runtime failures in case of buggy or malicious clients.
In order not to reinvent the wheel, an example with the parse combinator nom:
use nom::{
number::complete::le_u32,
bytes::complete::take,
error::ErrorKind,
IResult
};
use easy_http_request::DefaultHttpRequest;
use std::str::from_utf8;
#[derive(Debug, Clone)]
struct Test {
value: String
}
fn decode_len_value(bytes: &[u8]) -> IResult<&[u8], Test> {
let (buffer, len) = le_u32(bytes)?;
// take len-1 bytes because null char (\0) is accounted into len
let (remaining, val) = take(len-1)(buffer)?;
match from_utf8(val) {
Ok(strval) => Ok((remaining, Test {value: strval.to_owned()})),
Err(_) => Err(nom::Err::Error((remaining, ErrorKind::Char)))
}
}
fn main() {
let response = DefaultHttpRequest::get_from_url_str("http://localhost:5000/").unwrap().send().unwrap();
let result = decode_len_value(&response.body);
println!("{:?}", result);
}

Resources