use actix_web::{self, client, HttpMessage};
use futures::prelude::{async, await};
use openssl::sha::sha256;
use secp256k1::SecretKey;
use json;
use openssl::{rand, symm};
use std::str;
use std::str::FromStr;
use num_bigint::BigInt;
use std::convert::TryFrom;
use json::Value;
use actix_web::client::ClientRequestBuilder;
use std::borrow::Cow;
use actix_web::client::SendRequestError;
use openssl::symm::Cipher;
use std::error::Error;
use Empty;
use serde::{Serialize};
use actix_web::error::PayloadError;
use actix_web::http::StatusCode;
#[derive(Debug)]
pub enum ServiceError {
Generic(GenericError),
Service(Box<dyn Error>),
}
impl From<GenericError> for ServiceError {
fn from(err: GenericError) -> Self {
ServiceError::Generic(err)
}
}
#[derive(Debug)]
pub enum GenericError {
RequestSerialization(Box<dyn Error>),
RequestEncoding(Box<dyn Error>),
Transport(actix_web::Error),
RequestSend(SendRequestError),
ResponseRecv(PayloadError),
ResponseDecoding(Box<dyn Error>),
ResponseDeserialization(Box<dyn Error>),
}
impl From<actix_web::Error> for GenericError {
fn from(err: actix_web::Error) -> Self {
GenericError::Transport(err)
}
}
impl From<SendRequestError> for GenericError {
fn from(err: SendRequestError) -> Self {
GenericError::RequestSend(err)
}
}
impl From<PayloadError> for GenericError {
fn from(err: PayloadError) -> Self {
GenericError::ResponseRecv(err)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Balance {
live_snm: BigInt,
side_snm: BigInt,
live_eth: BigInt,
}
impl Balance {
#[inline]
pub fn live_snm(&self) -> &BigInt {
&self.live_snm
}
#[inline]
pub fn side_snm(&self) -> &BigInt {
&self.side_snm
}
#[inline]
pub fn live_eth(&self) -> &BigInt {
&self.live_eth
}
}
impl TryFrom<Value> for Balance {
type Error = Box<dyn Error>;
fn try_from(value: Value) -> Result<Self, Self::Error> {
let live_snm = value["liveBalance"]
.as_str()
.ok_or("`liveBalance` field is required")?;
let side_snm = value["sideBalance"]
.as_str()
.ok_or("`sideBalance` field is required")?;
let live_eth = value["liveEthBalance"]
.as_str()
.ok_or("`liveEthBalance` field is required")?;
let balance = Balance {
live_snm: BigInt::from_str(live_snm)?,
side_snm: BigInt::from_str(side_snm)?,
live_eth: BigInt::from_str(live_eth)?,
};
Ok(balance)
}
}
#[derive(Clone, Copy)]
pub struct AES256Cipher {
cipher: Cipher,
key: [u8; 32],
}
impl AES256Cipher {
pub fn new(key: [u8; 32]) -> Self {
Self {
cipher: symm::Cipher::aes_256_cfb128(),
key,
}
}
pub fn encode(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
let mut iv = vec![0u8; 16];
rand::rand_bytes(&mut iv[..])?;
let encrypted_message = symm::encrypt(self.cipher, &self.key, Some(&iv[..]), data)?;
let mut result = iv;
result.extend(encrypted_message.iter());
Ok(result)
}
pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
let result = symm::decrypt(self.cipher, &self.key, Some(&data[..16]), &data[16..])?;
Ok(result)
}
}
#[derive(Clone)]
pub struct Node {
secret: SecretKey,
base_uri: Cow<'static, str>,
cipher: AES256Cipher,
}
impl Node {
pub fn new(secret: SecretKey) -> Self {
Self {
secret,
base_uri: "http://localhost:15031".into(),
cipher: AES256Cipher::new(sha256(&secret[..])),
}
}
#[async]
pub fn balance(self) -> Result<Balance, ServiceError> {
let request = Empty{};
let response = await!(self.execute("TokenManagementServer/Balance".into(), request))?;
Ok(response)
}
#[async]
fn execute<T, R>(self, path: Cow<'static, str>, request: T) -> Result<R, ServiceError>
where
T: Serialize + 'static,
R: TryFrom<Value, Error = Box<dyn Error>>,
{
let body = json::to_vec(&request).map_err(|err| GenericError::RequestSerialization(err.into()))?;
let body = self.cipher.encode(&body).map_err(GenericError::RequestEncoding)?;
let request = self.make_request(&path)
.body(body)
.map_err(GenericError::Transport)?;
let response = await!(request.send())
.map_err(GenericError::RequestSend)?;
let body = await!(response.body()).map_err(GenericError::ResponseRecv)?;
let body = self.cipher.decode(&body).map_err(GenericError::ResponseDecoding)?;
if let StatusCode::OK = response.status() {
let value: Value = json::from_slice(&body).map_err(|err| GenericError::ResponseDeserialization(err.into()))?;
let response = R::try_from(value).map_err(|err| GenericError::ResponseDeserialization(err))?;
Ok(response)
} else {
Err(ServiceError::Service(String::from_utf8(body).unwrap_or("<invalid UTF8>".into()).into()))
}
}
#[inline]
fn make_request(&self, path: &str) -> ClientRequestBuilder {
client::post(format!("{}/{}", self.base_uri, path))
}
}