2020-01-05 16:11:00 +00:00
|
|
|
extern crate kuska_handshake;
|
|
|
|
extern crate kuska_ssb;
|
|
|
|
|
|
|
|
extern crate base64;
|
|
|
|
extern crate crossbeam;
|
2020-03-01 19:02:38 +00:00
|
|
|
extern crate regex;
|
2020-03-30 22:30:56 +00:00
|
|
|
extern crate structopt;
|
2020-01-05 16:11:00 +00:00
|
|
|
|
|
|
|
use std::fmt::Debug;
|
|
|
|
|
2020-01-21 12:34:58 +00:00
|
|
|
use async_std::io::{Read, Write};
|
2020-03-30 22:30:56 +00:00
|
|
|
use async_std::net::{TcpStream, UdpSocket};
|
2020-01-05 16:11:00 +00:00
|
|
|
|
2020-01-21 12:34:58 +00:00
|
|
|
use kuska_handshake::async_std::{handshake_client, BoxStream};
|
|
|
|
use kuska_ssb::api::{
|
2020-04-11 13:39:43 +00:00
|
|
|
dto::{CreateHistoryStreamIn, CreateStreamIn, LatestOut, WhoAmIOut},
|
|
|
|
ApiHelper,
|
2020-01-21 12:34:58 +00:00
|
|
|
};
|
|
|
|
use kuska_ssb::discovery::ssb_net_id;
|
|
|
|
use kuska_ssb::feed::{is_privatebox, privatebox_decipher, Feed, Message};
|
|
|
|
use kuska_ssb::keystore::from_patchwork_local;
|
|
|
|
use kuska_ssb::keystore::OwnedIdentity;
|
|
|
|
use kuska_ssb::rpc::{RecvMsg, RequestNo, RpcStream};
|
2020-01-05 16:11:00 +00:00
|
|
|
|
2020-03-01 19:02:38 +00:00
|
|
|
use regex::Regex;
|
2020-03-30 22:30:56 +00:00
|
|
|
use sodiumoxide::crypto::sign::ed25519;
|
2020-03-01 19:02:38 +00:00
|
|
|
use structopt::StructOpt;
|
|
|
|
|
2020-04-11 13:39:43 +00:00
|
|
|
type SolarResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
2020-01-05 22:15:43 +00:00
|
|
|
|
2020-03-01 09:51:12 +00:00
|
|
|
#[derive(Debug, StructOpt)]
|
|
|
|
#[structopt(name = "example", about = "An example of StructOpt usage.")]
|
|
|
|
struct Opt {
|
|
|
|
/// Connect to server
|
|
|
|
// format is: server:port:<server_id>
|
|
|
|
#[structopt(short, long)]
|
2020-03-01 19:02:38 +00:00
|
|
|
connect: Option<String>,
|
2020-03-01 09:51:12 +00:00
|
|
|
}
|
|
|
|
|
2020-04-11 13:39:43 +00:00
|
|
|
pub fn whoami_res_parse(body: &[u8]) -> SolarResult<WhoAmIOut> {
|
2020-01-21 12:34:58 +00:00
|
|
|
Ok(serde_json::from_slice(body)?)
|
|
|
|
}
|
2020-04-11 13:39:43 +00:00
|
|
|
pub fn message_res_parse(body: &[u8]) -> SolarResult<Message> {
|
2020-01-21 12:34:58 +00:00
|
|
|
Ok(Message::from_slice(body)?)
|
|
|
|
}
|
2020-04-11 13:39:43 +00:00
|
|
|
pub fn feed_res_parse(body: &[u8]) -> SolarResult<Feed> {
|
2020-01-21 12:34:58 +00:00
|
|
|
Ok(Feed::from_slice(&body)?)
|
|
|
|
}
|
2020-04-11 13:39:43 +00:00
|
|
|
pub fn latest_res_parse(body: &[u8]) -> SolarResult<LatestOut> {
|
2020-01-21 12:34:58 +00:00
|
|
|
Ok(serde_json::from_slice(body)?)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct AppError {
|
|
|
|
message: String,
|
|
|
|
}
|
|
|
|
impl AppError {
|
|
|
|
pub fn new(message: String) -> Self {
|
|
|
|
AppError { message }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl std::error::Error for AppError {
|
|
|
|
fn description(&self) -> &str {
|
|
|
|
&self.message
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl std::fmt::Display for AppError {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
write!(f, "{}", self.message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn get_async<'a, R, W, T, F>(
|
|
|
|
client: &mut ApiHelper<R, W>,
|
|
|
|
req_no: RequestNo,
|
|
|
|
f: F,
|
2020-04-11 13:39:43 +00:00
|
|
|
) -> SolarResult<T>
|
2020-01-05 16:11:00 +00:00
|
|
|
where
|
2020-01-21 12:34:58 +00:00
|
|
|
R: Read + Unpin,
|
|
|
|
W: Write + Unpin,
|
2020-04-11 13:39:43 +00:00
|
|
|
F: Fn(&[u8]) -> SolarResult<T>,
|
2020-01-21 12:34:58 +00:00
|
|
|
T: Debug,
|
2020-01-05 16:11:00 +00:00
|
|
|
{
|
|
|
|
loop {
|
2020-01-21 12:34:58 +00:00
|
|
|
let (id, msg) = client.rpc().recv().await?;
|
|
|
|
if id == req_no {
|
|
|
|
match msg {
|
2020-03-30 22:30:56 +00:00
|
|
|
RecvMsg::RpcResponse(_type, body) => {
|
2020-01-21 12:34:58 +00:00
|
|
|
return f(&body).map_err(|err| err.into());
|
|
|
|
}
|
|
|
|
RecvMsg::ErrorResponse(message) => {
|
|
|
|
return std::result::Result::Err(Box::new(AppError::new(message)));
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
println!("discarded message {}", id);
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-21 12:34:58 +00:00
|
|
|
async fn print_source_until_eof<'a, R, W, T, F>(
|
|
|
|
client: &mut ApiHelper<R, W>,
|
|
|
|
req_no: RequestNo,
|
|
|
|
f: F,
|
2020-04-11 13:39:43 +00:00
|
|
|
) -> SolarResult<()>
|
2020-01-05 16:11:00 +00:00
|
|
|
where
|
2020-01-21 12:34:58 +00:00
|
|
|
R: Read + Unpin,
|
|
|
|
W: Write + Unpin,
|
2020-04-11 13:39:43 +00:00
|
|
|
F: Fn(&[u8]) -> SolarResult<T>,
|
2020-01-21 12:34:58 +00:00
|
|
|
T: Debug + serde::Deserialize<'a>,
|
2020-01-05 16:11:00 +00:00
|
|
|
{
|
|
|
|
loop {
|
2020-01-21 12:34:58 +00:00
|
|
|
let (id, msg) = client.rpc().recv().await?;
|
|
|
|
if id == req_no {
|
|
|
|
match msg {
|
2020-03-30 22:30:56 +00:00
|
|
|
RecvMsg::RpcResponse(_type, body) => {
|
2020-01-21 12:34:58 +00:00
|
|
|
let display = f(&body)?;
|
|
|
|
println!("{:?}", display);
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
2020-01-21 12:34:58 +00:00
|
|
|
RecvMsg::ErrorResponse(message) => {
|
|
|
|
return std::result::Result::Err(Box::new(AppError::new(message)));
|
|
|
|
}
|
|
|
|
RecvMsg::CancelStreamRespose() => break,
|
|
|
|
_ => unreachable!(),
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
2020-01-21 12:34:58 +00:00
|
|
|
} else {
|
|
|
|
println!("discarded message {}", id);
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-21 12:34:58 +00:00
|
|
|
Ok(())
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[async_std::main]
|
2020-04-11 13:39:43 +00:00
|
|
|
async fn main() -> SolarResult<()> {
|
2020-01-05 16:11:00 +00:00
|
|
|
env_logger::init();
|
|
|
|
log::set_max_level(log::LevelFilter::max());
|
|
|
|
|
2020-03-01 09:51:12 +00:00
|
|
|
let OwnedIdentity { pk, sk, id } = from_patchwork_local().await.expect("read local secret");
|
|
|
|
println!("connecting with identity {}", id);
|
2020-03-30 22:30:56 +00:00
|
|
|
|
2020-03-01 09:51:12 +00:00
|
|
|
let opt = Opt::from_args();
|
2020-03-30 22:30:56 +00:00
|
|
|
let (ip, port, server_pk) = if let Some(connect) = opt.connect {
|
2020-03-01 19:02:38 +00:00
|
|
|
let connect: Vec<_> = connect.split(":").collect();
|
|
|
|
if connect.len() != 3 {
|
|
|
|
panic!("connection string should be server:port:id");
|
|
|
|
}
|
2020-03-30 22:30:56 +00:00
|
|
|
(
|
|
|
|
connect[0].to_string(),
|
|
|
|
connect[1].to_string(),
|
|
|
|
connect[2].to_string(),
|
|
|
|
)
|
2020-03-01 19:02:38 +00:00
|
|
|
} else {
|
|
|
|
println!("Waiting server broadcast...");
|
2020-03-30 22:30:56 +00:00
|
|
|
|
2020-03-01 19:02:38 +00:00
|
|
|
let socket = UdpSocket::bind("0.0.0.0:8008").await?;
|
|
|
|
socket.set_broadcast(true)?;
|
|
|
|
let mut buf = [0; 128];
|
|
|
|
let (amt, _) = socket.recv_from(&mut buf).await.unwrap();
|
|
|
|
|
2020-03-30 22:30:56 +00:00
|
|
|
let msg = String::from_utf8(buf[..amt].to_vec())?;
|
|
|
|
|
|
|
|
println!("got broadcasted {}", msg);
|
|
|
|
let broadcast_regexp =
|
|
|
|
r"net:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):([0-9]+)~shs:([0-9a-zA-Z=/]+)";
|
|
|
|
let captures = Regex::new(broadcast_regexp)
|
|
|
|
.unwrap()
|
|
|
|
.captures(&msg)
|
|
|
|
.unwrap();
|
|
|
|
(
|
|
|
|
captures[1].to_string(),
|
|
|
|
captures[2].to_string(),
|
|
|
|
captures[3].to_string(),
|
|
|
|
)
|
2020-03-01 19:02:38 +00:00
|
|
|
};
|
|
|
|
|
2020-03-30 22:30:56 +00:00
|
|
|
let server_pk =
|
|
|
|
ed25519::PublicKey::from_slice(&base64::decode(&server_pk)?).expect("bad public key");
|
2020-03-01 19:56:13 +00:00
|
|
|
let server_ipport = format!("{}:{}", ip, port);
|
|
|
|
|
2020-03-30 22:30:56 +00:00
|
|
|
println!("server_ip_port={}", server_ipport);
|
2020-03-01 19:56:13 +00:00
|
|
|
|
|
|
|
let mut socket = TcpStream::connect(server_ipport).await?;
|
2020-01-05 16:11:00 +00:00
|
|
|
|
2020-03-01 09:51:12 +00:00
|
|
|
let handshake = handshake_client(&mut socket, ssb_net_id(), pk, sk.clone(), server_pk).await?;
|
2020-01-05 16:11:00 +00:00
|
|
|
|
|
|
|
println!("💃 handshake complete");
|
|
|
|
|
|
|
|
let (box_stream_read, box_stream_write) =
|
2020-01-21 12:34:58 +00:00
|
|
|
BoxStream::from_handshake(&socket, &socket, handshake, 0x8000).split_read_write();
|
2020-01-05 16:11:00 +00:00
|
|
|
|
2020-01-21 12:34:58 +00:00
|
|
|
let mut client = ApiHelper::new(RpcStream::new(box_stream_read, box_stream_write));
|
2020-01-05 16:11:00 +00:00
|
|
|
|
2020-01-21 12:34:58 +00:00
|
|
|
let req_id = client.whoami_req_send().await?;
|
|
|
|
let whoami = get_async(&mut client, req_id, whoami_res_parse).await?.id;
|
2020-01-05 16:11:00 +00:00
|
|
|
|
2020-01-21 12:34:58 +00:00
|
|
|
println!("😊 server says hello to {}", whoami);
|
2020-01-05 16:11:00 +00:00
|
|
|
|
|
|
|
let mut line_buffer = String::new();
|
|
|
|
while let Ok(_) = std::io::stdin().read_line(&mut line_buffer) {
|
2020-01-21 12:34:58 +00:00
|
|
|
let args: Vec<String> = line_buffer
|
2020-01-05 16:11:00 +00:00
|
|
|
.replace("\n", "")
|
|
|
|
.split_whitespace()
|
|
|
|
.map(|arg| arg.to_string())
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
match (args[0].as_str(), args.len()) {
|
2020-01-21 12:34:58 +00:00
|
|
|
("exit", 1) => {
|
2020-01-05 16:11:00 +00:00
|
|
|
client.rpc().close().await?;
|
|
|
|
break;
|
|
|
|
}
|
2020-01-21 12:34:58 +00:00
|
|
|
("whoami", 1) => {
|
|
|
|
let req_id = client.whoami_req_send().await?;
|
|
|
|
let whoami = get_async(&mut client, req_id, whoami_res_parse).await?.id;
|
|
|
|
println!("{}", whoami);
|
|
|
|
}
|
|
|
|
("get", 2) => {
|
2020-01-05 16:11:00 +00:00
|
|
|
let msg_id = if args[1] == "any" {
|
|
|
|
"%TL34NIX8JpMJN+ubHWx6cRhIwEal8VqHdKVg2t6lFcg=.sha256".to_string()
|
|
|
|
} else {
|
|
|
|
args[1].clone()
|
|
|
|
};
|
2020-01-21 12:34:58 +00:00
|
|
|
let req_id = client.get_req_send(&msg_id).await?;
|
|
|
|
let msg = get_async(&mut client, req_id, message_res_parse).await?;
|
|
|
|
println!("{:?}", msg);
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
2020-01-21 12:34:58 +00:00
|
|
|
("user", 2) => {
|
|
|
|
let user_id = if args[1] == "me" { &whoami } else { &args[1] };
|
2020-01-05 16:11:00 +00:00
|
|
|
|
2020-04-11 13:39:43 +00:00
|
|
|
let args = CreateHistoryStreamIn::new(user_id.clone());
|
2020-01-21 12:34:58 +00:00
|
|
|
let req_id = client.create_history_stream_req_send(&args).await?;
|
|
|
|
print_source_until_eof(&mut client, req_id, feed_res_parse).await?;
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
2020-01-21 12:34:58 +00:00
|
|
|
("feed", 1) => {
|
2020-04-11 13:39:43 +00:00
|
|
|
let args = CreateStreamIn::default();
|
|
|
|
let req_id = client.create_feed_stream_req_send(&args).await?;
|
2020-01-21 12:34:58 +00:00
|
|
|
print_source_until_eof(&mut client, req_id, feed_res_parse).await?;
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
2020-01-21 12:34:58 +00:00
|
|
|
("latest", 1) => {
|
2020-04-11 13:39:43 +00:00
|
|
|
let req_id = client.latest_req_send().await?;
|
2020-01-21 12:34:58 +00:00
|
|
|
print_source_until_eof(&mut client, req_id, latest_res_parse).await?;
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
2020-01-21 12:34:58 +00:00
|
|
|
("private", 2) => {
|
|
|
|
let user_id = if args[1] == "me" { &whoami } else { &args[1] };
|
2020-01-05 16:11:00 +00:00
|
|
|
|
2020-01-21 12:34:58 +00:00
|
|
|
let show_private = |body: &[u8]| {
|
|
|
|
let msg = feed_res_parse(body)?.into_message()?;
|
2020-01-05 16:11:00 +00:00
|
|
|
if let serde_json::Value::String(content) = msg.content() {
|
|
|
|
if is_privatebox(&content) {
|
2020-01-21 12:34:58 +00:00
|
|
|
let ret = privatebox_decipher(&content, &sk)?.unwrap_or("".to_string());
|
2020-01-05 16:11:00 +00:00
|
|
|
return Ok(ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Ok("".to_string());
|
2020-01-21 12:34:58 +00:00
|
|
|
};
|
2020-01-05 16:11:00 +00:00
|
|
|
|
2020-04-11 13:39:43 +00:00
|
|
|
let args = CreateHistoryStreamIn::new(user_id.clone());
|
2020-01-21 12:34:58 +00:00
|
|
|
let req_id = client.create_history_stream_req_send(&args).await?;
|
2020-01-05 16:11:00 +00:00
|
|
|
|
2020-01-21 12:34:58 +00:00
|
|
|
print_source_until_eof(&mut client, req_id, show_private).await?;
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
2020-01-21 12:34:58 +00:00
|
|
|
_ => println!("unknown command {}", line_buffer),
|
2020-01-05 16:11:00 +00:00
|
|
|
}
|
|
|
|
line_buffer.clear();
|
|
|
|
}
|
|
|
|
Ok(())
|
2020-01-21 12:34:58 +00:00
|
|
|
}
|