use std::ffi::c_void;
use std::vec::Vec;
use crate::encoding::Binary;
use crate::errors::{generic_err, StdResult, SystemError};
#[cfg(feature = "iterator")]
use crate::iterator::{Order, KV};
use crate::memory::{alloc, build_region, consume_region, Region};
use crate::serde::from_slice;
use crate::traits::{Api, Querier, QuerierResult, ReadonlyStorage, Storage};
use crate::types::{CanonicalAddr, HumanAddr};
static KI: usize = 1024;
#[cfg(feature = "iterator")]
static DB_READ_KEY_BUFFER_LENGTH: usize = 64 * KI;
static DB_READ_VALUE_BUFFER_LENGTH: usize = 128 * KI;
static QUERY_RESULT_BUFFER_LENGTH: usize = 128 * KI;
static ADDR_BUFFER_LENGTH: usize = 90;
extern "C" {
fn db_read(key: *const c_void, value: *mut c_void) -> i32;
fn db_write(key: *const c_void, value: *mut c_void) -> i32;
fn db_remove(key: *const c_void) -> i32;
#[cfg(feature = "iterator")]
fn db_scan(start: *const c_void, end: *const c_void, order: i32) -> i32;
#[cfg(feature = "iterator")]
fn db_next(iterator_id: u32, key: *mut c_void, value: *mut c_void) -> i32;
fn canonicalize_address(human: *const c_void, canonical: *mut c_void) -> i32;
fn humanize_address(canonical: *const c_void, human: *mut c_void) -> i32;
fn query_chain(request: *const c_void, response: *mut c_void) -> i32;
}
pub struct ExternalStorage {}
impl ExternalStorage {
pub fn new() -> ExternalStorage {
ExternalStorage {}
}
pub fn get_with_result_length(
&self,
key: &[u8],
result_length: usize,
) -> StdResult<Option<Vec<u8>>> {
let key = build_region(key);
let key_ptr = &*key as *const Region as *const c_void;
let value_ptr = alloc(result_length);
let read = unsafe { db_read(key_ptr, value_ptr) };
if read == -1_000_001 {
return Err(generic_err("Allocated memory too small to hold the database value for the given key. \
You can specify custom result buffer lengths by using ExternalStorage.get_with_result_length explicitely."));
} else if read == -1_001_001 {
return Ok(None);
} else if read < 0 {
return Err(generic_err(format!(
"Error reading from database. Error code: {}",
read
)));
}
let data = unsafe { consume_region(value_ptr) }?;
Ok(Some(data))
}
}
impl ReadonlyStorage for ExternalStorage {
fn get(&self, key: &[u8]) -> StdResult<Option<Vec<u8>>> {
self.get_with_result_length(key, DB_READ_VALUE_BUFFER_LENGTH)
}
#[cfg(feature = "iterator")]
fn range(
&self,
start: Option<&[u8]>,
end: Option<&[u8]>,
order: Order,
) -> StdResult<Box<dyn Iterator<Item = StdResult<KV>>>> {
let start = start.map(|s| build_region(s));
let start_ptr = match start {
Some(reg) => &*reg as *const Region as *const c_void,
None => std::ptr::null(),
};
let end = end.map(|e| build_region(e));
let end_ptr = match end {
Some(reg) => &*reg as *const Region as *const c_void,
None => std::ptr::null(),
};
let order = order as i32;
let scan_result = unsafe { db_scan(start_ptr, end_ptr, order) };
if scan_result < 0 {
return Err(generic_err(format!(
"Error creating iterator (via db_scan). Error code: {}",
scan_result
)));
}
let iter = ExternalIterator {
iterator_id: scan_result as u32, };
Ok(Box::new(iter))
}
}
impl Storage for ExternalStorage {
fn set(&mut self, key: &[u8], value: &[u8]) -> StdResult<()> {
let key = build_region(key);
let key_ptr = &*key as *const Region as *const c_void;
let mut value = build_region(value);
let value_ptr = &mut *value as *mut Region as *mut c_void;
let result = unsafe { db_write(key_ptr, value_ptr) };
if result < 0 {
return Err(generic_err(format!(
"Error writing to database. Error code: {}",
result
)));
}
Ok(())
}
fn remove(&mut self, key: &[u8]) -> StdResult<()> {
let key = build_region(key);
let key_ptr = &*key as *const Region as *const c_void;
let result = unsafe { db_remove(key_ptr) };
if result < 0 {
return Err(generic_err(format!(
"Error deleting from database. Error code: {}",
result
)));
}
Ok(())
}
}
#[cfg(feature = "iterator")]
struct ExternalIterator {
iterator_id: u32,
}
#[cfg(feature = "iterator")]
impl Iterator for ExternalIterator {
type Item = StdResult<KV>;
fn next(&mut self) -> Option<Self::Item> {
let key_ptr = alloc(DB_READ_KEY_BUFFER_LENGTH);
let value_ptr = alloc(DB_READ_VALUE_BUFFER_LENGTH);
let db_next_result = unsafe { db_next(self.iterator_id, key_ptr, value_ptr) };
if db_next_result < 0 {
let result = Err(generic_err(format!(
"Unknown error from db_next: {}",
db_next_result
)));
return Some(result);
}
let key = unsafe { consume_region(key_ptr).unwrap() };
let value = unsafe { consume_region(value_ptr).unwrap() };
if key.is_empty() {
return None;
}
Some(Ok((key, value)))
}
}
#[derive(Copy, Clone)]
pub struct ExternalApi {}
impl ExternalApi {
pub fn new() -> ExternalApi {
ExternalApi {}
}
}
impl Api for ExternalApi {
fn canonical_address(&self, human: &HumanAddr) -> StdResult<CanonicalAddr> {
let send = build_region(human.as_str().as_bytes());
let send_ptr = &*send as *const Region as *const c_void;
let canon = alloc(ADDR_BUFFER_LENGTH);
let read = unsafe { canonicalize_address(send_ptr, canon) };
if read < 0 {
return Err(generic_err("canonicalize_address returned error"));
}
let out = unsafe { consume_region(canon)? };
Ok(CanonicalAddr(Binary(out)))
}
fn human_address(&self, canonical: &CanonicalAddr) -> StdResult<HumanAddr> {
let send = build_region(canonical.as_slice());
let send_ptr = &*send as *const Region as *const c_void;
let human = alloc(ADDR_BUFFER_LENGTH);
let read = unsafe { humanize_address(send_ptr, human) };
if read < 0 {
return Err(generic_err("humanize_address returned error"));
}
let out = unsafe { consume_region(human)? };
let result = unsafe { String::from_utf8_unchecked(out) };
Ok(HumanAddr(result))
}
}
#[derive(Copy, Clone)]
pub struct ExternalQuerier {}
impl ExternalQuerier {
pub fn new() -> ExternalQuerier {
ExternalQuerier {}
}
}
impl Querier for ExternalQuerier {
fn raw_query(&self, bin_request: &[u8]) -> QuerierResult {
let req = build_region(bin_request);
let request_ptr = &*req as *const Region as *const c_void;
let response_ptr = alloc(QUERY_RESULT_BUFFER_LENGTH);
let result_code = unsafe { query_chain(request_ptr, response_ptr) };
if result_code < 0 {
return Err(SystemError::Unknown {});
}
let process = |region_ptr| -> StdResult<QuerierResult> {
let out = unsafe { consume_region(region_ptr)? };
let parsed: QuerierResult = from_slice(&out)?;
Ok(parsed)
};
match process(response_ptr) {
Ok(api_response) => api_response,
Err(err) => Ok(Err(err)),
}
}
}