use std::{
marker::PhantomData,
time::SystemTime,
};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use rand::{Rng, distributions::Alphanumeric};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::json;
use generic_api_client::{http::{*, header::HeaderValue}, websocket::*};
use crate::traits::*;
pub type BitFlyerRequestResult<T> = Result<T, BitFlyerRequestError>;
pub type BitFlyerRequestError = RequestError<&'static str, BitFlyerHandlerError>;
pub enum BitFlyerOption {
Default,
Key(String),
Secret(String),
HttpUrl(BitFlyerHttpUrl),
HttpAuth(bool),
RequestConfig(RequestConfig),
WebSocketUrl(BitFlyerWebSocketUrl),
WebSocketAuth(bool),
WebSocketChannels(Vec<String>),
WebSocketConfig(WebSocketConfig),
}
#[derive(Clone, Debug)]
pub struct BitFlyerOptions {
pub key: Option<String>,
pub secret: Option<String>,
pub http_url: BitFlyerHttpUrl,
pub http_auth: bool,
pub request_config: RequestConfig,
pub websocket_url: BitFlyerWebSocketUrl,
pub websocket_auth: bool,
pub websocket_channels: Vec<String>,
pub websocket_config: WebSocketConfig,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum BitFlyerHttpUrl {
Default,
None,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[non_exhaustive]
pub enum BitFlyerWebSocketUrl {
Default,
None,
}
#[derive(Deserialize, Debug)]
pub struct BitFlyerChannelMessage {
pub channel: String,
pub message: serde_json::Value,
}
#[derive(Debug)]
pub enum BitFlyerHandlerError {
ApiError(serde_json::Value),
ParseError,
}
pub struct BitFlyerRequestHandler<'a, R: DeserializeOwned> {
options: BitFlyerOptions,
_phantom: PhantomData<&'a R>,
}
pub struct BitFlyerWebSocketHandler {
message_handler: Box<dyn FnMut(BitFlyerChannelMessage) + Send>,
auth_id: Option<String>,
options: BitFlyerOptions,
}
impl<'a, B, R> RequestHandler<B> for BitFlyerRequestHandler<'a, R>
where
B: Serialize,
R: DeserializeOwned,
{
type Successful = R;
type Unsuccessful = BitFlyerHandlerError;
type BuildError = &'static str;
fn request_config(&self) -> RequestConfig {
let mut config = self.options.request_config.clone();
if self.options.http_url != BitFlyerHttpUrl::None {
config.url_prefix = self.options.http_url.as_str().to_owned();
}
config
}
fn build_request(&self, mut builder: RequestBuilder, request_body: &Option<B>, _: u8) -> Result<Request, Self::BuildError> {
if let Some(body) = request_body {
let json = serde_json::to_vec(body).or(Err("could not serialize body as application/json"))?;
builder = builder
.header(header::CONTENT_TYPE, "application/json")
.body(json);
}
let mut request = builder.build().or(Err("failed to build request"))?;
if self.options.http_auth {
let time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let timestamp = time.as_millis() as u64;
let mut path = request.url().path().to_owned();
if let Some(query) = request.url().query() {
path.push('?');
path.push_str(query)
}
let body = request.body()
.and_then(|body| body.as_bytes())
.map(String::from_utf8_lossy)
.unwrap_or_default();
let sign_contents = format!("{}{}{}{}", timestamp, request.method(), path, body);
let secret = self.options.secret.as_deref().ok_or("API secret not set")?;
let mut hmac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).unwrap(); hmac.update(sign_contents.as_bytes());
let signature = hex::encode(hmac.finalize().into_bytes());
let key = HeaderValue::from_str(self.options.key.as_deref().ok_or("API key not set")?).or(
Err("invalid character in API key")
)?;
let headers = request.headers_mut();
headers.insert("ACCESS-KEY", key);
headers.insert("ACCESS-TIMESTAMP", HeaderValue::from(timestamp));
headers.insert("ACCESS-SIGN", HeaderValue::from_str(&signature).unwrap()); headers.insert(header::CONTENT_TYPE, HeaderValue::from_str("application/json").unwrap()); }
Ok(request)
}
fn handle_response(&self, status: StatusCode, _: HeaderMap, response_body: Bytes) -> Result<Self::Successful, Self::Unsuccessful> {
if status.is_success() {
serde_json::from_slice(&response_body).map_err(|error| {
log::debug!("Failed to parse response due to an error: {}", error);
BitFlyerHandlerError::ParseError
})
} else {
let error = match serde_json::from_slice(&response_body) {
Ok(parsed_error) => BitFlyerHandlerError::ApiError(parsed_error),
Err(error) => {
log::debug!("Failed to parse error response due to an error: {}", error);
BitFlyerHandlerError::ParseError
}
};
Err(error)
}
}
}
impl WebSocketHandler for BitFlyerWebSocketHandler {
fn websocket_config(&self) -> WebSocketConfig {
let mut config = self.options.websocket_config.clone();
if self.options.websocket_url != BitFlyerWebSocketUrl::None {
config.url_prefix = self.options.websocket_url.as_str().to_owned();
}
config
}
fn handle_start(&mut self) -> Vec<WebSocketMessage> {
if self.options.websocket_auth {
if let Some(key) = self.options.key.as_deref() {
if let Some(secret) = self.options.secret.as_deref() {
let time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let timestamp = time.as_millis() as u64;
let nonce: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(16)
.map(char::from)
.collect();
let mut hmac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).unwrap(); hmac.update(format!("{timestamp}{nonce}").as_bytes());
let signature = hex::encode(hmac.finalize().into_bytes());
let id = format!("_auth{}", time.as_nanos());
self.auth_id = Some(id.clone());
return vec![WebSocketMessage::Text(json!({
"method": "auth",
"params": {
"api_key": key,
"timestamp": timestamp,
"nonce": nonce,
"signature": signature,
},
"id": id,
}).to_string())];
} else {
log::debug!("API secret not set.");
};
} else {
log::debug!("API key not set.");
};
}
self.message_subscribe()
}
fn handle_message(&mut self, message: WebSocketMessage) -> Vec<WebSocketMessage> {
#[derive(Deserialize)]
struct Message {
#[allow(dead_code)]
jsonrpc: String, method: Option<String>,
result: Option<serde_json::Value>,
params: Option<BitFlyerChannelMessage>,
id: Option<String>,
}
match message {
WebSocketMessage::Text(message) => {
let message: Message = match serde_json::from_str(&message) {
Ok(message) => message,
Err(_) => {
log::debug!("Invalid JSON-RPC message received");
return vec![];
},
};
if self.options.websocket_auth && self.auth_id == message.id {
if message.result == Some(serde_json::Value::Bool(true)) {
log::debug!("WebSocket authentication successful");
return self.message_subscribe();
} else {
log::debug!("WebSocket authentication unsuccessful");
}
self.auth_id = None;
} else if message.method.as_deref() == Some("channelMessage") {
if let Some(channel_message) = message.params {
(self.message_handler)(channel_message);
}
}
},
WebSocketMessage::Binary(_) => log::debug!("Unexpected binary message received"),
WebSocketMessage::Ping(_) | WebSocketMessage::Pong(_) => (),
}
vec![]
}
}
impl BitFlyerWebSocketHandler {
#[inline]
fn message_subscribe(&self) -> Vec<WebSocketMessage> {
self.options.websocket_channels.clone().into_iter().map(|channel| {
WebSocketMessage::Text(json!({ "method": "subscribe", "params": { "channel": channel } }).to_string())
}).collect()
}
}
impl BitFlyerHttpUrl {
#[inline(always)]
fn as_str(&self) -> &'static str {
match self {
Self::Default => "https://api.bitflyer.com",
Self::None => "",
}
}
}
impl BitFlyerWebSocketUrl {
#[inline(always)]
fn as_str(&self) -> &'static str {
match self {
Self::Default => "wss://ws.lightstream.bitflyer.com",
Self::None => "",
}
}
}
impl HandlerOptions for BitFlyerOptions {
type OptionItem = BitFlyerOption;
fn update(&mut self, option: Self::OptionItem) {
match option {
BitFlyerOption::Default => (),
BitFlyerOption::Key(v) => self.key = Some(v),
BitFlyerOption::Secret(v) => self.secret = Some(v),
BitFlyerOption::HttpUrl(v) => self.http_url = v,
BitFlyerOption::HttpAuth(v) => self.http_auth = v,
BitFlyerOption::RequestConfig(v) => self.request_config = v,
BitFlyerOption::WebSocketUrl(v) => self.websocket_url = v,
BitFlyerOption::WebSocketAuth(v) => self.websocket_auth = v,
BitFlyerOption::WebSocketChannels(v) => self.websocket_channels = v,
BitFlyerOption::WebSocketConfig(v) => self.websocket_config = v,
}
}
}
impl Default for BitFlyerOptions {
fn default() -> Self {
let mut websocket_config = WebSocketConfig::new();
websocket_config.ignore_duplicate_during_reconnection = true;
Self {
key: None,
secret: None,
http_url: BitFlyerHttpUrl::Default,
http_auth: false,
request_config: RequestConfig::default(),
websocket_url: BitFlyerWebSocketUrl::Default,
websocket_auth: false,
websocket_channels: vec![],
websocket_config,
}
}
}
impl<'a, R, B> HttpOption<'a, R, B> for BitFlyerOption
where
R: DeserializeOwned + 'a,
B: Serialize,
{
type RequestHandler = BitFlyerRequestHandler<'a, R>;
#[inline(always)]
fn request_handler(options: Self::Options) -> Self::RequestHandler {
BitFlyerRequestHandler::<'a, R> {
options,
_phantom: PhantomData,
}
}
}
impl<H: FnMut(BitFlyerChannelMessage) + Send + 'static> WebSocketOption<H> for BitFlyerOption {
type WebSocketHandler = BitFlyerWebSocketHandler;
#[inline(always)]
fn websocket_handler(handler: H, options: Self::Options) -> Self::WebSocketHandler {
BitFlyerWebSocketHandler {
message_handler: Box::new(handler),
auth_id: None,
options,
}
}
}
impl HandlerOption for BitFlyerOption {
type Options = BitFlyerOptions;
}
impl Default for BitFlyerOption {
fn default() -> Self {
Self::Default
}
}