minimal server wip
This commit is contained in:
parent
893e5d348b
commit
2434cb2018
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "kuska-ssb"
|
name = "kuska-ssb"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
authors = ["Dhole <dhole@riseup.net>", "Adria Massanet <adria@codecontext.io>"]
|
authors = ["Dhole <dhole@riseup.net>", "Adria Massanet <adria@codecontext.io>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ kuska-handshake = { git = "https://github.com/Kuska-ssb/kuska-handshake", branch
|
||||||
sodiumoxide = { git = "https://github.com/Dhole/sodiumoxidez", branch = "extra" }
|
sodiumoxide = { git = "https://github.com/Dhole/sodiumoxidez", branch = "extra" }
|
||||||
base64 = "0.11.0"
|
base64 = "0.11.0"
|
||||||
hex = "0.4.0"
|
hex = "0.4.0"
|
||||||
async-std = { version = "1.1.0", features=["unstable","attributes"] }
|
async-std = { version = "1.4.0", features=["unstable","attributes"] }
|
||||||
crossbeam = "0.7.3"
|
crossbeam = "0.7.3"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
env_logger = "0.7.1"
|
env_logger = "0.7.1"
|
||||||
|
|
|
@ -1,232 +0,0 @@
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
use async_std::sync::{Receiver,Sender,channel};
|
|
||||||
use async_std::task;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::cell::{RefCell};
|
|
||||||
use std::sync::{Arc,Mutex};
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
use signal_hook::{iterator::Signals, SIGTERM, SIGINT, SIGHUP, SIGQUIT};
|
|
||||||
|
|
||||||
use actix_web::{get, web, App, HttpServer, Responder};
|
|
||||||
|
|
||||||
use async_std::io::{Read,Write};
|
|
||||||
|
|
||||||
use kuska_handshake::async_std::{BoxStream,handshake_client,TokioCompatExt,TokioCompatExtRead,TokioCompatExtWrite};
|
|
||||||
use kuska_ssb::rpc::{RequestNo,RecvMsg,RpcStream};
|
|
||||||
use kuska_ssb::patchwork::*;
|
|
||||||
|
|
||||||
use tokio::net::TcpStream;
|
|
||||||
|
|
||||||
type AnyResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
|
||||||
|
|
||||||
async fn get_async<'a,R,W,T,F> (client: &mut ApiClient<R,W>, req_no : RequestNo, f : F) -> AnyResult<T>
|
|
||||||
where
|
|
||||||
R: Read+Unpin,
|
|
||||||
W: Write+Unpin,
|
|
||||||
F: Fn(&[u8])->Result<T>,
|
|
||||||
T: Debug
|
|
||||||
{
|
|
||||||
loop {
|
|
||||||
let (id,msg) = client.rpc().recv().await?;
|
|
||||||
if id == req_no {
|
|
||||||
match msg {
|
|
||||||
RecvMsg::BodyResponse(body) => {
|
|
||||||
return f(&body).map_err(|err| err.into());
|
|
||||||
}
|
|
||||||
RecvMsg::ErrorResponse(message) => {
|
|
||||||
println!(" 😢 Failed {:}",message);
|
|
||||||
}
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run_task<R:Read+Unpin,W:Write+Unpin>(api : &mut ApiClient<R,W>, _command: &str) -> AnyResult<bool> {
|
|
||||||
let req_id = api.send_whoami().await?;
|
|
||||||
let whoami = get_async(api,req_id,parse_whoami).await?.id;
|
|
||||||
|
|
||||||
println!("{}",whoami);
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sync_loop(command_receiver: Receiver<String>, stop_receiver : Receiver<bool>) -> AnyResult<()>{
|
|
||||||
|
|
||||||
/*
|
|
||||||
sync loop functionality
|
|
||||||
friend_list = []
|
|
||||||
discovered_peers = []
|
|
||||||
connected_peers = []
|
|
||||||
loop {
|
|
||||||
foreach peer in discoverd_peer not in conected_peers {
|
|
||||||
peer.handshake.createboxstream
|
|
||||||
peer.feed_callback = { |msg|
|
|
||||||
process_feed_message(msg)
|
|
||||||
}
|
|
||||||
for each friend in friend_list {
|
|
||||||
peer.createUserStream(friend)
|
|
||||||
}
|
|
||||||
conected_peers.push(peer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
let IdentitySecret{pk,sk,..} = IdentitySecret::from_local_config()
|
|
||||||
.expect("read local secret");
|
|
||||||
|
|
||||||
let tokio_socket : TcpStream = TcpStream::connect("127.0.0.1:8008").await?;
|
|
||||||
let mut asyncstd_socket = TokioCompatExt::wrap(tokio_socket);
|
|
||||||
|
|
||||||
let handshake = handshake_client(&mut asyncstd_socket, ssb_net_id(), pk, sk.clone(), pk).await?;
|
|
||||||
|
|
||||||
|
|
||||||
let mut tokio_socket = asyncstd_socket.into_inner();
|
|
||||||
let (read,write) = tokio_socket.split();
|
|
||||||
|
|
||||||
let read = TokioCompatExtRead::wrap(read);
|
|
||||||
let write = TokioCompatExtWrite::wrap(write);
|
|
||||||
|
|
||||||
let (box_stream_read, box_stream_write) =
|
|
||||||
BoxStream::from_handshake(read, write, handshake, 0x8000)
|
|
||||||
.split_read_write();
|
|
||||||
|
|
||||||
let rpc = RpcStream::new(box_stream_read, box_stream_write);
|
|
||||||
let mut api = ApiClient::new(rpc);
|
|
||||||
|
|
||||||
let mut commands_queue : Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
|
|
||||||
if !stop_receiver.is_empty() {
|
|
||||||
stop_receiver.recv().await;
|
|
||||||
println!("finished loop");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// read all pending requests
|
|
||||||
while !command_receiver.is_empty() {
|
|
||||||
if let Some(msg) = command_receiver.recv().await {
|
|
||||||
commands_queue.push(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(command) = commands_queue.pop() {
|
|
||||||
run_task(&mut api,&command).await?;
|
|
||||||
} else {
|
|
||||||
task::sleep(Duration::from_secs(1)).await;
|
|
||||||
println!("waiting!");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async fn command_loop(command_receiver: Receiver<String>, stop_receiver : Receiver<bool>) -> AnyResult<()>{
|
|
||||||
|
|
||||||
let IdentitySecret{pk,sk,..} = IdentitySecret::from_local_config()
|
|
||||||
.expect("read local secret");
|
|
||||||
|
|
||||||
let tokio_socket : TcpStream = TcpStream::connect("127.0.0.1:8008").await?;
|
|
||||||
let mut asyncstd_socket = TokioCompatExt::wrap(tokio_socket);
|
|
||||||
|
|
||||||
let handshake = handshake_client(&mut asyncstd_socket, ssb_net_id(), pk, sk.clone(), pk).await?;
|
|
||||||
|
|
||||||
println!("💃 handshake complete");
|
|
||||||
|
|
||||||
let mut tokio_socket = asyncstd_socket.into_inner();
|
|
||||||
let (read,write) = tokio_socket.split();
|
|
||||||
|
|
||||||
let read = TokioCompatExtRead::wrap(read);
|
|
||||||
let write = TokioCompatExtWrite::wrap(write);
|
|
||||||
|
|
||||||
let (box_stream_read, box_stream_write) =
|
|
||||||
BoxStream::from_handshake(read, write, handshake, 0x8000)
|
|
||||||
.split_read_write();
|
|
||||||
|
|
||||||
let rpc = RpcStream::new(box_stream_read, box_stream_write);
|
|
||||||
let mut api = ApiClient::new(rpc);
|
|
||||||
|
|
||||||
let mut commands_queue : Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
|
|
||||||
if !stop_receiver.is_empty() {
|
|
||||||
stop_receiver.recv().await;
|
|
||||||
println!("finished loop");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// read all pending requests
|
|
||||||
while !command_receiver.is_empty() {
|
|
||||||
if let Some(msg) = command_receiver.recv().await {
|
|
||||||
commands_queue.push(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(command) = commands_queue.pop() {
|
|
||||||
run_task(&mut api,&command).await?;
|
|
||||||
} else {
|
|
||||||
task::sleep(Duration::from_secs(1)).await;
|
|
||||||
println!("waiting!");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref COMMAND_SENDER : Arc<Mutex<RefCell<Option<Sender<String>>>>> = Arc::new(Mutex::new(RefCell::new(None)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[get("/{id}/{name}/index.html")]
|
|
||||||
async fn index(info: web::Path<(u32, String)>) -> impl Responder {
|
|
||||||
COMMAND_SENDER.lock().unwrap().borrow().as_ref().unwrap().send("hola".to_owned()).await;
|
|
||||||
format!("Hello {}! id:{}", info.1, info.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async fn web_handler() -> std::io::Result<()> {
|
|
||||||
HttpServer::new(|| App::new().service(index))
|
|
||||||
.bind("127.0.0.1:8080")?
|
|
||||||
.run()
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sigterm_handler(stop_sender : Sender<bool>, count : usize, ) {
|
|
||||||
let signals = Signals::new(&[SIGTERM, SIGHUP, SIGINT, SIGQUIT]).expect("cannot capture SIGTERM");
|
|
||||||
loop {
|
|
||||||
if signals.pending().next().is_some() {
|
|
||||||
for _ in 0..count {
|
|
||||||
stop_sender.send(true).await;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
task::sleep(Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::main]
|
|
||||||
async fn main() {
|
|
||||||
println!("started");
|
|
||||||
|
|
||||||
let (stop_sender, stop_receiver) = channel::<bool>(1);
|
|
||||||
let (command_sender, command_receiver) = channel::<String>(1);
|
|
||||||
COMMAND_SENDER.lock().unwrap().replace(Some(command_sender));
|
|
||||||
|
|
||||||
let future_sigterm = sigterm_handler(stop_sender,1);
|
|
||||||
let future_loop = command_loop(command_receiver,stop_receiver.clone());
|
|
||||||
let future_web = web_handler();
|
|
||||||
|
|
||||||
let (_,loop_res,_) = futures::join!(future_sigterm,future_loop,future_web);
|
|
||||||
loop_res.expect("main loop failed");
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,61 +6,106 @@ extern crate crossbeam;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use async_std::io::{Read,Write};
|
use async_std::io::{Read, Write};
|
||||||
use async_std::net::TcpStream;
|
use async_std::net::TcpStream;
|
||||||
|
|
||||||
use kuska_handshake::async_std::{handshake_client,BoxStream};
|
use kuska_handshake::async_std::{handshake_client, BoxStream};
|
||||||
use kuska_ssb::rpc::{RecvMsg,RequestNo,RpcStream};
|
use kuska_ssb::feed::{is_privatebox, privatebox_decipher, Feed, Message};
|
||||||
use kuska_ssb::patchwork::*;
|
use kuska_ssb::patchwork::*;
|
||||||
use kuska_ssb::feed::{is_privatebox,privatebox_decipher};
|
use kuska_ssb::patchwork::{ApiHelper, LatestUserMessage, WhoAmI};
|
||||||
use kuska_ssb::patchwork::{parse_feed,parse_latest,parse_message,parse_whoami};
|
use kuska_ssb::rpc::{RecvMsg, RequestNo, RpcStream};
|
||||||
|
|
||||||
type AnyResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
type AnyResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
async fn get_async<'a,R,W,T,F> (client: &mut ApiClient<R,W>, req_no : RequestNo, f : F) -> AnyResult<T>
|
pub fn whoami_res_parse(body: &[u8]) -> AnyResult<WhoAmI> {
|
||||||
|
Ok(serde_json::from_slice(body)?)
|
||||||
|
}
|
||||||
|
pub fn message_res_parse(body: &[u8]) -> AnyResult<Message> {
|
||||||
|
Ok(Message::from_slice(body)?)
|
||||||
|
}
|
||||||
|
pub fn feed_res_parse(body: &[u8]) -> AnyResult<Feed> {
|
||||||
|
Ok(Feed::from_slice(&body)?)
|
||||||
|
}
|
||||||
|
pub fn latest_res_parse(body: &[u8]) -> AnyResult<LatestUserMessage> {
|
||||||
|
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,
|
||||||
|
) -> AnyResult<T>
|
||||||
where
|
where
|
||||||
R: Read+Unpin,
|
R: Read + Unpin,
|
||||||
W: Write+Unpin,
|
W: Write + Unpin,
|
||||||
F: Fn(&[u8])->Result<T>,
|
F: Fn(&[u8]) -> AnyResult<T>,
|
||||||
T: Debug
|
T: Debug,
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
let (id,msg) = client.rpc().recv().await?;
|
let (id, msg) = client.rpc().recv().await?;
|
||||||
if id == req_no {
|
if id == req_no {
|
||||||
match msg {
|
match msg {
|
||||||
RecvMsg::BodyResponse(body) => {
|
RecvMsg::BodyResponse(body) => {
|
||||||
return f(&body).map_err(|err| err.into());
|
return f(&body).map_err(|err| err.into());
|
||||||
}
|
}
|
||||||
RecvMsg::ErrorResponse(message) => {
|
RecvMsg::ErrorResponse(message) => {
|
||||||
println!(" 😢 Failed {:}",message);
|
return std::result::Result::Err(Box::new(AppError::new(message)));
|
||||||
}
|
}
|
||||||
_ => unreachable!()
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
println!("discarded message {}", id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn print_source_until_eof<'a,R,W,T,F> (client: &mut ApiClient<R,W>, req_no : RequestNo, f : F) -> AnyResult<()>
|
async fn print_source_until_eof<'a, R, W, T, F>(
|
||||||
|
client: &mut ApiHelper<R, W>,
|
||||||
|
req_no: RequestNo,
|
||||||
|
f: F,
|
||||||
|
) -> AnyResult<()>
|
||||||
where
|
where
|
||||||
R: Read+Unpin,
|
R: Read + Unpin,
|
||||||
W: Write+Unpin,
|
W: Write + Unpin,
|
||||||
F: Fn(&[u8])->Result<T>,
|
F: Fn(&[u8]) -> AnyResult<T>,
|
||||||
T: Debug+serde::Deserialize<'a>
|
T: Debug + serde::Deserialize<'a>,
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
let (id,msg) = client.rpc().recv().await?;
|
let (id, msg) = client.rpc().recv().await?;
|
||||||
if id == req_no {
|
if id == req_no {
|
||||||
match msg {
|
match msg {
|
||||||
RecvMsg::BodyResponse(body) => {
|
RecvMsg::BodyResponse(body) => {
|
||||||
let display = f(&body)?;
|
let display = f(&body)?;
|
||||||
println!("{:?}",display);
|
println!("{:?}", display);
|
||||||
}
|
}
|
||||||
RecvMsg::ErrorResponse(message) => {
|
RecvMsg::ErrorResponse(message) => {
|
||||||
println!(" 😢 Failed {:}",message);
|
return std::result::Result::Err(Box::new(AppError::new(message)));
|
||||||
}
|
}
|
||||||
RecvMsg::CancelStreamRespose() => break,
|
RecvMsg::CancelStreamRespose() => break,
|
||||||
_ => unreachable!()
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
println!("discarded message {}", id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -71,97 +116,90 @@ async fn main() -> AnyResult<()> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
log::set_max_level(log::LevelFilter::max());
|
log::set_max_level(log::LevelFilter::max());
|
||||||
|
|
||||||
let IdentitySecret{pk,sk,..} = IdentitySecret::from_local_config()
|
let IdentitySecret { pk, sk, .. } =
|
||||||
.expect("read local secret");
|
IdentitySecret::from_local_config().expect("read local secret");
|
||||||
|
|
||||||
let mut socket = TcpStream::connect("127.0.0.1:8008").await?;
|
|
||||||
|
|
||||||
|
let mut socket = TcpStream::connect("127.0.0.1:8080").await?;
|
||||||
let handshake = handshake_client(&mut socket, ssb_net_id(), pk, sk.clone(), pk).await?;
|
let handshake = handshake_client(&mut socket, ssb_net_id(), pk, sk.clone(), pk).await?;
|
||||||
|
|
||||||
println!("💃 handshake complete");
|
println!("💃 handshake complete");
|
||||||
|
|
||||||
let (box_stream_read, box_stream_write) =
|
let (box_stream_read, box_stream_write) =
|
||||||
BoxStream::from_handshake(&socket,&socket,handshake, 0x8000)
|
BoxStream::from_handshake(&socket, &socket, handshake, 0x8000).split_read_write();
|
||||||
.split_read_write();
|
|
||||||
|
|
||||||
let mut client = ApiClient::new(RpcStream::new(box_stream_read, box_stream_write));
|
let mut client = ApiHelper::new(RpcStream::new(box_stream_read, box_stream_write));
|
||||||
|
|
||||||
let req_id = client.send_whoami().await?;
|
let req_id = client.whoami_req_send().await?;
|
||||||
let whoami = get_async(&mut client,req_id,parse_whoami).await?.id;
|
let whoami = get_async(&mut client, req_id, whoami_res_parse).await?.id;
|
||||||
|
|
||||||
println!("😊 server says hello to {}",whoami);
|
println!("😊 server says hello to {}", whoami);
|
||||||
|
|
||||||
let mut line_buffer = String::new();
|
let mut line_buffer = String::new();
|
||||||
while let Ok(_) = std::io::stdin().read_line(&mut line_buffer) {
|
while let Ok(_) = std::io::stdin().read_line(&mut line_buffer) {
|
||||||
|
let args: Vec<String> = line_buffer
|
||||||
let args : Vec<String> = line_buffer
|
|
||||||
.replace("\n", "")
|
.replace("\n", "")
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.map(|arg| arg.to_string())
|
.map(|arg| arg.to_string())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match (args[0].as_str(), args.len()) {
|
match (args[0].as_str(), args.len()) {
|
||||||
("exit",1) => {
|
("exit", 1) => {
|
||||||
client.rpc().close().await?;
|
client.rpc().close().await?;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
("get",2) => {
|
("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) => {
|
||||||
let msg_id = if args[1] == "any" {
|
let msg_id = if args[1] == "any" {
|
||||||
"%TL34NIX8JpMJN+ubHWx6cRhIwEal8VqHdKVg2t6lFcg=.sha256".to_string()
|
"%TL34NIX8JpMJN+ubHWx6cRhIwEal8VqHdKVg2t6lFcg=.sha256".to_string()
|
||||||
} else {
|
} else {
|
||||||
args[1].clone()
|
args[1].clone()
|
||||||
};
|
};
|
||||||
let req_id = client.send_get(&msg_id).await?;
|
let req_id = client.get_req_send(&msg_id).await?;
|
||||||
let msg = get_async(&mut client,req_id,parse_message).await?;
|
let msg = get_async(&mut client, req_id, message_res_parse).await?;
|
||||||
println!("{:?}",msg);
|
println!("{:?}", msg);
|
||||||
}
|
}
|
||||||
("user",2) => {
|
("user", 2) => {
|
||||||
let user_id = if args[1] == "me" {
|
let user_id = if args[1] == "me" { &whoami } else { &args[1] };
|
||||||
&whoami
|
|
||||||
} else {
|
|
||||||
&args[1]
|
|
||||||
};
|
|
||||||
|
|
||||||
let args = CreateHistoryStreamArgs::new(&user_id);
|
let args = CreateHistoryStreamArgs::new(user_id.clone());
|
||||||
let req_id = client.send_create_history_stream(&args).await?;
|
let req_id = client.create_history_stream_req_send(&args).await?;
|
||||||
print_source_until_eof(&mut client, req_id, parse_feed).await?;
|
print_source_until_eof(&mut client, req_id, feed_res_parse).await?;
|
||||||
}
|
}
|
||||||
("feed",1) => {
|
("feed", 1) => {
|
||||||
let args = CreateStreamArgs::default();
|
let args = CreateStreamArgs::default();
|
||||||
let req_id = client.send_create_feed_stream(&args).await?;
|
let req_id = client.send_create_feed_stream(&args).await?;
|
||||||
print_source_until_eof(&mut client, req_id, parse_feed).await?;
|
print_source_until_eof(&mut client, req_id, feed_res_parse).await?;
|
||||||
}
|
}
|
||||||
("latest",1) => {
|
("latest", 1) => {
|
||||||
let req_id = client.send_latest().await?;
|
let req_id = client.send_latest().await?;
|
||||||
print_source_until_eof(&mut client, req_id, parse_latest).await?;
|
print_source_until_eof(&mut client, req_id, latest_res_parse).await?;
|
||||||
}
|
}
|
||||||
("private",2) => {
|
("private", 2) => {
|
||||||
let user_id = if args[1] == "me" {
|
let user_id = if args[1] == "me" { &whoami } else { &args[1] };
|
||||||
&whoami
|
|
||||||
} else {
|
|
||||||
&args[1]
|
|
||||||
};
|
|
||||||
|
|
||||||
let show_private = |body: &[u8]| {
|
let show_private = |body: &[u8]| {
|
||||||
let msg = parse_feed(body)?.into_message()?;
|
let msg = feed_res_parse(body)?.into_message()?;
|
||||||
if let serde_json::Value::String(content) = msg.content() {
|
if let serde_json::Value::String(content) = msg.content() {
|
||||||
if is_privatebox(&content) {
|
if is_privatebox(&content) {
|
||||||
let ret = privatebox_decipher(&content, &sk)?
|
let ret = privatebox_decipher(&content, &sk)?.unwrap_or("".to_string());
|
||||||
.unwrap_or("".to_string());
|
|
||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok("".to_string());
|
return Ok("".to_string());
|
||||||
};
|
};
|
||||||
|
|
||||||
let args = CreateHistoryStreamArgs::new(&user_id);
|
let args = CreateHistoryStreamArgs::new(user_id.clone());
|
||||||
let req_id = client.send_create_history_stream(&args).await?;
|
let req_id = client.create_history_stream_req_send(&args).await?;
|
||||||
|
|
||||||
print_source_until_eof(&mut client, req_id, show_private).await?;
|
print_source_until_eof(&mut client, req_id, show_private).await?;
|
||||||
}
|
}
|
||||||
_ => println!("unknown command {}",line_buffer),
|
_ => println!("unknown command {}", line_buffer),
|
||||||
}
|
}
|
||||||
line_buffer.clear();
|
line_buffer.clear();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ impl std::fmt::Display for Error {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{:?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::error::Error for Error { }
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod sodium;
|
|
||||||
mod error;
|
mod error;
|
||||||
|
mod sodium;
|
||||||
|
|
||||||
pub use sodium::ToSodiumObject;
|
pub use error::{Error, Result};
|
||||||
pub use error::{Error,Result};
|
pub use sodium::{ToSodiumObject, ToSsbId};
|
||||||
|
|
|
@ -1,69 +1,74 @@
|
||||||
use sodiumoxide::crypto::sign::ed25519;
|
|
||||||
use sodiumoxide::crypto::hash::sha256;
|
|
||||||
use base64;
|
use base64;
|
||||||
|
use sodiumoxide::crypto::hash::sha256;
|
||||||
|
use sodiumoxide::crypto::sign::ed25519;
|
||||||
|
|
||||||
use super::error::{Error,Result};
|
use super::error::{Error, Result};
|
||||||
|
|
||||||
const CURVE_ED25519_SUFFIX : &str = ".ed25519";
|
const CURVE_ED25519_SUFFIX: &str = ".ed25519";
|
||||||
const ED25519_SIGNATURE_SUFFIX : &str = ".sig.ed25519";
|
const ED25519_SIGNATURE_SUFFIX: &str = ".sig.ed25519";
|
||||||
const SHA256_SUFFIX : &str = ".sha256";
|
const SHA256_SUFFIX: &str = ".sha256";
|
||||||
|
|
||||||
pub trait ToSodiumObject {
|
pub trait ToSodiumObject {
|
||||||
fn to_ed25519_pk(&self) -> Result<ed25519::PublicKey>;
|
fn to_ed25519_pk(&self) -> Result<ed25519::PublicKey>;
|
||||||
fn to_ed25519_sk(&self) -> Result<ed25519::SecretKey>;
|
fn to_ed25519_sk(&self) -> Result<ed25519::SecretKey>;
|
||||||
fn to_ed25519_sk_no_suffix(&self) -> Result<ed25519::SecretKey>;
|
fn to_ed25519_sk_no_suffix(&self) -> Result<ed25519::SecretKey>;
|
||||||
fn to_ed25519_signature(&self) -> Result<ed25519::Signature>;
|
fn to_ed25519_signature(&self) -> Result<ed25519::Signature>;
|
||||||
fn to_sha256(&self) -> Result<sha256::Digest>;
|
fn to_sha256(&self) -> Result<sha256::Digest>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ToSsbId {
|
||||||
|
fn to_ssb_id(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ToSsbId for ed25519::PublicKey {
|
||||||
|
fn to_ssb_id(&self) -> String {
|
||||||
|
format!("{}{}", base64::encode(self), CURVE_ED25519_SUFFIX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToSodiumObject for str {
|
impl ToSodiumObject for str {
|
||||||
fn to_ed25519_pk(self : &str) -> Result<ed25519::PublicKey> {
|
fn to_ed25519_pk(self: &str) -> Result<ed25519::PublicKey> {
|
||||||
if !self.ends_with(CURVE_ED25519_SUFFIX) {
|
if !self.ends_with(CURVE_ED25519_SUFFIX) {
|
||||||
return Err(Error::InvalidSuffix);
|
return Err(Error::InvalidSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
let key_len = self.len()-CURVE_ED25519_SUFFIX.len();
|
let key_len = self.len() - CURVE_ED25519_SUFFIX.len();
|
||||||
let bytes = base64::decode(&self[..key_len])?;
|
let bytes = base64::decode(&self[..key_len])?;
|
||||||
|
|
||||||
ed25519::PublicKey::from_slice(&bytes)
|
ed25519::PublicKey::from_slice(&bytes).ok_or_else(|| Error::BadPublicKey)
|
||||||
.ok_or_else(|| Error::BadPublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_ed25519_sk(self : &str) -> Result<ed25519::SecretKey> {
|
|
||||||
if !self.ends_with(CURVE_ED25519_SUFFIX) {
|
|
||||||
return Err(Error::InvalidSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
let key_len = self.len()-CURVE_ED25519_SUFFIX.len();
|
|
||||||
let bytes = base64::decode(&self[..key_len])?;
|
|
||||||
|
|
||||||
ed25519::SecretKey::from_slice(&bytes)
|
|
||||||
.ok_or_else(|| Error::BadSecretKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_ed25519_sk_no_suffix(self : &str) -> Result<ed25519::SecretKey> {
|
fn to_ed25519_sk(self: &str) -> Result<ed25519::SecretKey> {
|
||||||
|
if !self.ends_with(CURVE_ED25519_SUFFIX) {
|
||||||
|
return Err(Error::InvalidSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_len = self.len() - CURVE_ED25519_SUFFIX.len();
|
||||||
|
let bytes = base64::decode(&self[..key_len])?;
|
||||||
|
|
||||||
|
ed25519::SecretKey::from_slice(&bytes).ok_or_else(|| Error::BadSecretKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_ed25519_sk_no_suffix(self: &str) -> Result<ed25519::SecretKey> {
|
||||||
let bytes = base64::decode(&self[..])?;
|
let bytes = base64::decode(&self[..])?;
|
||||||
|
|
||||||
ed25519::SecretKey::from_slice(&bytes)
|
ed25519::SecretKey::from_slice(&bytes).ok_or_else(|| Error::BadSecretKey)
|
||||||
.ok_or_else(|| Error::BadSecretKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_sha256(self : &str) -> Result<sha256::Digest> {
|
fn to_sha256(self: &str) -> Result<sha256::Digest> {
|
||||||
if !self.ends_with(SHA256_SUFFIX) {
|
if !self.ends_with(SHA256_SUFFIX) {
|
||||||
return Err(Error::InvalidSuffix);
|
return Err(Error::InvalidSuffix);
|
||||||
}
|
}
|
||||||
let key = base64::decode(&self[..self.len()-SHA256_SUFFIX.len()])?;
|
let key = base64::decode(&self[..self.len() - SHA256_SUFFIX.len()])?;
|
||||||
|
|
||||||
sha256::Digest::from_slice(&key)
|
sha256::Digest::from_slice(&key).ok_or(Error::InvalidDigest)
|
||||||
.ok_or(Error::InvalidDigest)
|
|
||||||
}
|
}
|
||||||
fn to_ed25519_signature(self : &str) -> Result<ed25519::Signature> {
|
fn to_ed25519_signature(self: &str) -> Result<ed25519::Signature> {
|
||||||
if !self.ends_with(ED25519_SIGNATURE_SUFFIX) {
|
if !self.ends_with(ED25519_SIGNATURE_SUFFIX) {
|
||||||
return Err(Error::InvalidSuffix);
|
return Err(Error::InvalidSuffix);
|
||||||
}
|
}
|
||||||
let signature = base64::decode(&self[..self.len()-ED25519_SIGNATURE_SUFFIX.len()])?;
|
let signature = base64::decode(&self[..self.len() - ED25519_SIGNATURE_SUFFIX.len()])?;
|
||||||
|
|
||||||
ed25519::Signature::from_slice(&signature)
|
ed25519::Signature::from_slice(&signature).ok_or(Error::CannotCreateSignature)
|
||||||
.ok_or(Error::CannotCreateSignature)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,5 @@ impl std::fmt::Display for Error {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{:?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::error::Error for Error { }
|
impl std::error::Error for Error {}
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
|
187
src/db/feeds.rs
187
src/db/feeds.rs
|
@ -1,44 +1,46 @@
|
||||||
|
use super::error::{Error, Result};
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::fs::{File,OpenOptions};
|
|
||||||
use std::io::SeekFrom;
|
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use super::error::{Error,Result};
|
use std::io::SeekFrom;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub struct FeedsStorage {
|
pub struct FeedsStorage {
|
||||||
path : PathBuf
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FeedsStorage {
|
impl FeedsStorage {
|
||||||
fn filename(&self, user_id : &str) -> PathBuf {
|
fn filename(&self, user_id: &str) -> PathBuf {
|
||||||
let name = user_id.chars().map(|ch| match ch {
|
let name = user_id
|
||||||
'+' => '-',
|
.chars()
|
||||||
'/' => '_',
|
.map(|ch| match ch {
|
||||||
_ => ch,
|
'+' => '-',
|
||||||
}).collect::<String>();
|
'/' => '_',
|
||||||
|
_ => ch,
|
||||||
|
})
|
||||||
|
.collect::<String>();
|
||||||
let mut path = PathBuf::new();
|
let mut path = PathBuf::new();
|
||||||
path.push(&self.path);
|
path.push(&self.path);
|
||||||
path.push(name);
|
path.push(name);
|
||||||
|
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(path : PathBuf) -> Self{
|
pub fn new(path: PathBuf) -> Self {
|
||||||
FeedsStorage { path }
|
FeedsStorage { path }
|
||||||
}
|
}
|
||||||
pub fn user(&self, user_id : String) -> FeedStorage {
|
pub fn user(&self, user_id: String) -> FeedStorage {
|
||||||
FeedStorage {
|
FeedStorage {
|
||||||
path : self.filename(&user_id)
|
path: self.filename(&user_id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FeedStorage {
|
pub struct FeedStorage {
|
||||||
path : PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FeedStorage {
|
impl FeedStorage {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
raw feed storage structure:
|
raw feed storage structure:
|
||||||
- last sequence in feed - 32 bits be
|
- last sequence in feed - 32 bits be
|
||||||
|
@ -53,11 +55,11 @@ impl FeedStorage {
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(&self.path)?;
|
.open(&self.path)?;
|
||||||
|
|
||||||
// check and update feed sequence number
|
// check and update feed sequence number
|
||||||
let created = file.seek(SeekFrom::End(0))? == 0;
|
let created = file.seek(SeekFrom::End(0))? == 0;
|
||||||
if created {
|
if created {
|
||||||
if seq_no != 1{
|
if seq_no != 1 {
|
||||||
return Err(Error::InvalidSequenceNo);
|
return Err(Error::InvalidSequenceNo);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -66,46 +68,43 @@ impl FeedStorage {
|
||||||
return Err(Error::InvalidSequenceNo);
|
return Err(Error::InvalidSequenceNo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.set_last_seq(&mut file,seq_no)?;
|
self.set_last_seq(&mut file, seq_no)?;
|
||||||
|
|
||||||
file.seek(SeekFrom::End(0))?;
|
file.seek(SeekFrom::End(0))?;
|
||||||
|
|
||||||
// write feed size dummy
|
// write feed size dummy
|
||||||
file.write_all( &(0 as u32).to_be_bytes()[..] )?;
|
file.write_all(&(0 as u32).to_be_bytes()[..])?;
|
||||||
|
|
||||||
// write compressed feed
|
// write compressed feed
|
||||||
let offset = file.seek(SeekFrom::Current(0))?;
|
let offset = file.seek(SeekFrom::Current(0))?;
|
||||||
let mut wtr = snap::Writer::new(file);
|
let mut wtr = snap::Writer::new(file);
|
||||||
io::copy(&mut feed.as_bytes(), &mut wtr)?;
|
io::copy(&mut feed.as_bytes(), &mut wtr)?;
|
||||||
|
|
||||||
let mut file = wtr.into_inner().map_err(
|
let mut file = wtr
|
||||||
|err| Error::CompressionError(format!("{:?}",err))
|
.into_inner()
|
||||||
)?;
|
.map_err(|err| Error::CompressionError(format!("{:?}", err)))?;
|
||||||
let len = file.seek(SeekFrom::Current(0))?- offset;
|
let len = file.seek(SeekFrom::Current(0))? - offset;
|
||||||
|
|
||||||
// write feed size
|
// write feed size
|
||||||
file.write_all( &(len as u32).to_be_bytes()[..] )?;
|
file.write_all(&(len as u32).to_be_bytes()[..])?;
|
||||||
|
|
||||||
file.seek(SeekFrom::End(-((8+len) as i64)))?;
|
file.seek(SeekFrom::End(-((8 + len) as i64)))?;
|
||||||
file.write_all( &(len as u32).to_be_bytes()[..] )?;
|
file.write_all(&(len as u32).to_be_bytes()[..])?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_seq(&self) -> Result<u32> {
|
pub fn last_seq(&self) -> Result<u32> {
|
||||||
|
|
||||||
if !self.path.exists() {
|
if !self.path.exists() {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new().read(true).open(&self.path)?;
|
||||||
.read(true)
|
|
||||||
.open(&self.path)?;
|
|
||||||
self.get_last_seq(&mut file)
|
self.get_last_seq(&mut file)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_last_seq(&self, file: &mut File) -> Result<u32> {
|
fn get_last_seq(&self, file: &mut File) -> Result<u32> {
|
||||||
let mut file_seq_no = [0u8;4];
|
let mut file_seq_no = [0u8; 4];
|
||||||
file.seek(SeekFrom::Start(0))?;
|
file.seek(SeekFrom::Start(0))?;
|
||||||
file.read_exact(&mut file_seq_no[..])?;
|
file.read_exact(&mut file_seq_no[..])?;
|
||||||
Ok(u32::from_be_bytes(file_seq_no))
|
Ok(u32::from_be_bytes(file_seq_no))
|
||||||
|
@ -113,50 +112,45 @@ impl FeedStorage {
|
||||||
|
|
||||||
fn set_last_seq(&self, file: &mut File, seq_no: u32) -> Result<()> {
|
fn set_last_seq(&self, file: &mut File, seq_no: u32) -> Result<()> {
|
||||||
file.seek(SeekFrom::Start(0))?;
|
file.seek(SeekFrom::Start(0))?;
|
||||||
file.write_all( &seq_no.to_be_bytes()[..] )?;
|
file.write_all(&seq_no.to_be_bytes()[..])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> Result<FeedStorageIterator> {
|
pub fn iter(&self) -> Result<FeedStorageIterator> {
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new().read(true).open(&self.path)?;
|
||||||
.read(true)
|
|
||||||
.open(&self.path)?;
|
|
||||||
|
|
||||||
let last_seq_no = self.get_last_seq(&mut file)?;
|
let last_seq_no = self.get_last_seq(&mut file)?;
|
||||||
|
|
||||||
Ok(FeedStorageIterator{
|
Ok(FeedStorageIterator {
|
||||||
file,
|
file,
|
||||||
current_seq_no : 0,
|
current_seq_no: 0,
|
||||||
last_seq_no,
|
last_seq_no,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rev_iter(&self) -> Result<FeedStorageReverseIterator> {
|
pub fn rev_iter(&self) -> Result<FeedStorageReverseIterator> {
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new().read(true).open(&self.path)?;
|
||||||
.read(true)
|
|
||||||
.open(&self.path)?;
|
|
||||||
|
|
||||||
let last_seq_no = self.get_last_seq(&mut file)?;
|
let last_seq_no = self.get_last_seq(&mut file)?;
|
||||||
file.seek(SeekFrom::End(-4))?;
|
file.seek(SeekFrom::End(-4))?;
|
||||||
|
|
||||||
Ok(FeedStorageReverseIterator{
|
Ok(FeedStorageReverseIterator {
|
||||||
file,
|
file,
|
||||||
current_seq_no : last_seq_no,
|
current_seq_no: last_seq_no,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq,Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct Feed {
|
pub struct Feed {
|
||||||
pub seq_no : u32,
|
pub seq_no: u32,
|
||||||
pub value : String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FeedStorageIterator {
|
pub struct FeedStorageIterator {
|
||||||
file : File,
|
file: File,
|
||||||
current_seq_no : u32,
|
current_seq_no: u32,
|
||||||
last_seq_no : u32,
|
last_seq_no: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for FeedStorageIterator {
|
impl Iterator for FeedStorageIterator {
|
||||||
|
@ -164,13 +158,12 @@ impl Iterator for FeedStorageIterator {
|
||||||
|
|
||||||
// next() is the only required method
|
// next() is the only required method
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
|
||||||
if self.current_seq_no >= self.last_seq_no {
|
if self.current_seq_no >= self.last_seq_no {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// read compressed size
|
// read compressed size
|
||||||
let mut size_buf = [0u8;4];
|
let mut size_buf = [0u8; 4];
|
||||||
if let Err(err) = self.file.read_exact(&mut size_buf[..]) {
|
if let Err(err) = self.file.read_exact(&mut size_buf[..]) {
|
||||||
return Some(Err(Error::Io(err)));
|
return Some(Err(Error::Io(err)));
|
||||||
}
|
}
|
||||||
|
@ -183,8 +176,8 @@ impl Iterator for FeedStorageIterator {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rdr = snap::Reader::new(&compressed[..]);
|
let mut rdr = snap::Reader::new(&compressed[..]);
|
||||||
let mut plaintext : Vec<u8> = Vec::new();
|
let mut plaintext: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
if let Err(err) = io::copy(&mut rdr, &mut plaintext) {
|
if let Err(err) = io::copy(&mut rdr, &mut plaintext) {
|
||||||
return Some(Err(Error::Io(err)));
|
return Some(Err(Error::Io(err)));
|
||||||
}
|
}
|
||||||
|
@ -200,15 +193,18 @@ impl Iterator for FeedStorageIterator {
|
||||||
self.current_seq_no += 1;
|
self.current_seq_no += 1;
|
||||||
let ret = match String::from_utf8(plaintext) {
|
let ret = match String::from_utf8(plaintext) {
|
||||||
Err(err) => Err(Error::Utf8(err)),
|
Err(err) => Err(Error::Utf8(err)),
|
||||||
Ok(value) => Ok(Feed { seq_no : self.current_seq_no, value })
|
Ok(value) => Ok(Feed {
|
||||||
|
seq_no: self.current_seq_no,
|
||||||
|
value,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
Some(ret)
|
Some(ret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FeedStorageReverseIterator {
|
pub struct FeedStorageReverseIterator {
|
||||||
file : File,
|
file: File,
|
||||||
current_seq_no : u32,
|
current_seq_no: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for FeedStorageReverseIterator {
|
impl Iterator for FeedStorageReverseIterator {
|
||||||
|
@ -216,18 +212,17 @@ impl Iterator for FeedStorageReverseIterator {
|
||||||
|
|
||||||
// next() is the only required method
|
// next() is the only required method
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
|
||||||
if self.current_seq_no == 0 {
|
if self.current_seq_no == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// read compressed size
|
// read compressed size
|
||||||
let mut size_buf = [0u8;4];
|
let mut size_buf = [0u8; 4];
|
||||||
if let Err(err) = self.file.read_exact(&mut size_buf[..]) {
|
if let Err(err) = self.file.read_exact(&mut size_buf[..]) {
|
||||||
return Some(Err(Error::Io(err)));
|
return Some(Err(Error::Io(err)));
|
||||||
}
|
}
|
||||||
let size = u32::from_be_bytes(size_buf);
|
let size = u32::from_be_bytes(size_buf);
|
||||||
if let Err(err) = self.file.seek(SeekFrom::Current(-((size+8) as i64))) {
|
if let Err(err) = self.file.seek(SeekFrom::Current(-((size + 8) as i64))) {
|
||||||
return Some(Err(Error::Io(err)));
|
return Some(Err(Error::Io(err)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,25 +241,28 @@ impl Iterator for FeedStorageReverseIterator {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rdr = snap::Reader::new(&compressed[..]);
|
let mut rdr = snap::Reader::new(&compressed[..]);
|
||||||
let mut plaintext : Vec<u8> = Vec::new();
|
let mut plaintext: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
if let Err(err) = io::copy(&mut rdr, &mut plaintext) {
|
if let Err(err) = io::copy(&mut rdr, &mut plaintext) {
|
||||||
return Some(Err(Error::Io(err)));
|
return Some(Err(Error::Io(err)));
|
||||||
}
|
}
|
||||||
// prepare offset for the next read
|
// prepare offset for the next read
|
||||||
if let Err(err) = self.file.seek(SeekFrom::Current(-((size+8) as i64))) {
|
if let Err(err) = self.file.seek(SeekFrom::Current(-((size + 8) as i64))) {
|
||||||
return Some(Err(Error::Io(err)));
|
return Some(Err(Error::Io(err)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = match String::from_utf8(plaintext) {
|
let ret = match String::from_utf8(plaintext) {
|
||||||
Err(err) => Err(Error::Utf8(err)),
|
Err(err) => Err(Error::Utf8(err)),
|
||||||
Ok(value) => Ok(Feed { seq_no : self.current_seq_no, value })
|
Ok(value) => Ok(Feed {
|
||||||
|
seq_no: self.current_seq_no,
|
||||||
|
value,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.current_seq_no -= 1;
|
self.current_seq_no -= 1;
|
||||||
|
|
||||||
Some(ret)
|
Some(ret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -282,8 +280,8 @@ mod test {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut tmp_folder = std::env::temp_dir();
|
let mut tmp_folder = std::env::temp_dir();
|
||||||
tmp_folder.push(name);
|
tmp_folder.push(name);
|
||||||
|
|
||||||
std::fs::create_dir(&tmp_folder)?;
|
std::fs::create_dir(&tmp_folder)?;
|
||||||
|
|
||||||
Ok(tmp_folder)
|
Ok(tmp_folder)
|
||||||
|
@ -294,31 +292,40 @@ mod test {
|
||||||
let user_id = "@ZFWw+UclcUgYi081/C8lhgH+KQ9s7YJRoOYGnzxW/JQ=.ed25519";
|
let user_id = "@ZFWw+UclcUgYi081/C8lhgH+KQ9s7YJRoOYGnzxW/JQ=.ed25519";
|
||||||
let feeds = FeedsStorage::new(rand_folder()?);
|
let feeds = FeedsStorage::new(rand_folder()?);
|
||||||
let feed = feeds.user(user_id.to_owned());
|
let feed = feeds.user(user_id.to_owned());
|
||||||
|
|
||||||
let f1 = Feed{seq_no:1, value:"123".to_string()};
|
|
||||||
let f2 = Feed{seq_no:2, value:"8181".to_string()};
|
|
||||||
let f3 = Feed{seq_no:3, value:"182881".to_string()};
|
|
||||||
|
|
||||||
assert_eq!(0,feed.last_seq()?);
|
let f1 = Feed {
|
||||||
|
seq_no: 1,
|
||||||
|
value: "123".to_string(),
|
||||||
|
};
|
||||||
|
let f2 = Feed {
|
||||||
|
seq_no: 2,
|
||||||
|
value: "8181".to_string(),
|
||||||
|
};
|
||||||
|
let f3 = Feed {
|
||||||
|
seq_no: 3,
|
||||||
|
value: "182881".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(0, feed.last_seq()?);
|
||||||
feed.append(f1.seq_no, &f1.value)?;
|
feed.append(f1.seq_no, &f1.value)?;
|
||||||
assert_eq!(1,feed.last_seq()?);
|
assert_eq!(1, feed.last_seq()?);
|
||||||
feed.append(f2.seq_no, &f2.value)?;
|
feed.append(f2.seq_no, &f2.value)?;
|
||||||
assert_eq!(2,feed.last_seq()?);
|
assert_eq!(2, feed.last_seq()?);
|
||||||
feed.append(f3.seq_no, &f3.value)?;
|
feed.append(f3.seq_no, &f3.value)?;
|
||||||
assert_eq!(3,feed.last_seq()?);
|
assert_eq!(3, feed.last_seq()?);
|
||||||
|
|
||||||
let mut it = feed.iter()?;
|
let mut it = feed.iter()?;
|
||||||
assert_eq!(it.next().unwrap()?,f1);
|
assert_eq!(it.next().unwrap()?, f1);
|
||||||
assert_eq!(it.next().unwrap()?,f2);
|
assert_eq!(it.next().unwrap()?, f2);
|
||||||
assert_eq!(it.next().unwrap()?,f3);
|
assert_eq!(it.next().unwrap()?, f3);
|
||||||
assert_eq!(it.next().is_none(),true);
|
assert_eq!(it.next().is_none(), true);
|
||||||
|
|
||||||
let mut rev_it = feed.rev_iter()?;
|
let mut rev_it = feed.rev_iter()?;
|
||||||
assert_eq!(rev_it.next().unwrap()?,f3);
|
assert_eq!(rev_it.next().unwrap()?, f3);
|
||||||
assert_eq!(rev_it.next().unwrap()?,f2);
|
assert_eq!(rev_it.next().unwrap()?, f2);
|
||||||
assert_eq!(rev_it.next().unwrap()?,f1);
|
assert_eq!(rev_it.next().unwrap()?, f1);
|
||||||
assert_eq!(rev_it.next().is_none(),true);
|
assert_eq!(rev_it.next().is_none(), true);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
mod error;
|
mod error;
|
||||||
pub mod feeds;
|
mod feeds;
|
||||||
|
|
||||||
pub use error::{Error,Result};
|
pub use error::{Error, Result};
|
||||||
|
pub use feeds::{FeedStorageIterator, FeedStorageReverseIterator, FeedsStorage};
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use super::error::{Error, Result};
|
||||||
use super::message::Message;
|
use super::message::Message;
|
||||||
use super::ssb_sha256;
|
use super::ssb_sha256;
|
||||||
use super::error::{Error,Result};
|
use std::time::SystemTime;
|
||||||
|
use std::time::UNIX_EPOCH;
|
||||||
|
|
||||||
#[derive(Debug,Serialize,Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Feed {
|
pub struct Feed {
|
||||||
pub key: String,
|
pub key: String,
|
||||||
pub value: Value,
|
pub value: Value,
|
||||||
|
@ -14,18 +14,32 @@ pub struct Feed {
|
||||||
pub rts: Option<f64>,
|
pub rts: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Feed {
|
impl ToString for Feed {
|
||||||
pub fn into_message(self) -> Result<Message> {
|
fn to_string(&self) -> String {
|
||||||
Message::from_value(self.value)
|
serde_json::to_string(self).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Feed {
|
impl Feed {
|
||||||
type Err = Error;
|
pub fn into_message(self) -> Result<Message> {
|
||||||
|
Message::from_value(self.value)
|
||||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
}
|
||||||
let feed : Feed = serde_json::from_str(&s)?;
|
pub fn new(m: Message) -> Self {
|
||||||
let digest = format!("%{}.sha256",base64::encode(&ssb_sha256(&feed.value)?));
|
let key = format!("%{}.sha256", base64::encode(&ssb_sha256(&m.value).unwrap()));
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs_f64();
|
||||||
|
Feed {
|
||||||
|
key,
|
||||||
|
value: m.value,
|
||||||
|
timestamp,
|
||||||
|
rts: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_slice(s: &[u8]) -> Result<Self> {
|
||||||
|
let feed: Feed = serde_json::from_slice(&s)?;
|
||||||
|
let digest = format!("%{}.sha256", base64::encode(&ssb_sha256(&feed.value)?));
|
||||||
|
|
||||||
if digest != feed.key {
|
if digest != feed.key {
|
||||||
return Err(Error::FeedDigestMismatch);
|
return Err(Error::FeedDigestMismatch);
|
||||||
|
@ -38,11 +52,11 @@ impl FromStr for Feed {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_verify_feed_integrity() -> Result<()> {
|
fn test_verify_feed_integrity() -> Result<()> {
|
||||||
let feed = r#"{"key":"%Cg0ZpZ8cV85G8UIIropgBOvM8+Srlv9LSGDNGnpdK44=.sha256","value":{"previous":"%seUEAo7PTyA7vNwnOrmGIsUFfpyRzOvzGVv1QCb/Fz8=.sha256","author":"@BIbVppzlrNiRJogxDYz3glUS7G4s4D4NiXiPEAEzxdE=.ed25519","sequence":37,"timestamp":1439392020612,"hash":"sha256","content":{"type":"post","text":"@paul real time replies didn't work.","repliesTo":"%xWKunF6nXD7XMC+D4cjwDMZWmBnmRu69w9T25iLNa1Q=.sha256","mentions":["%7UKRfZb2u8al4tYWHqM55R9xpE/KKVh9U0M6BdugGt4=.sha256"],"recps":[{"link":"@hxGxqPrplLjRG2vtjQL87abX4QKqeLgCwQpS730nNwE=.ed25519","name":"paul"}]},"signature":"gGxSPdBJZxp6x5f3HzQGoQSeSdh/C5AtymIn+miWa+lcC6DdqpRSgaeH9KHeLf+/CKhU6REYIpWaLr4CKDMfCg==.sig.ed25519"},"timestamp":1573574678194,"rts":1439392020612}"#;
|
let feed = r#"{"key":"%Cg0ZpZ8cV85G8UIIropgBOvM8+Srlv9LSGDNGnpdK44=.sha256","value":{"previous":"%seUEAo7PTyA7vNwnOrmGIsUFfpyRzOvzGVv1QCb/Fz8=.sha256","author":"@BIbVppzlrNiRJogxDYz3glUS7G4s4D4NiXiPEAEzxdE=.ed25519","sequence":37,"timestamp":1439392020612,"hash":"sha256","content":{"type":"post","text":"@paul real time replies didn't work.","repliesTo":"%xWKunF6nXD7XMC+D4cjwDMZWmBnmRu69w9T25iLNa1Q=.sha256","mentions":["%7UKRfZb2u8al4tYWHqM55R9xpE/KKVh9U0M6BdugGt4=.sha256"],"recps":[{"link":"@hxGxqPrplLjRG2vtjQL87abX4QKqeLgCwQpS730nNwE=.ed25519","name":"paul"}]},"signature":"gGxSPdBJZxp6x5f3HzQGoQSeSdh/C5AtymIn+miWa+lcC6DdqpRSgaeH9KHeLf+/CKhU6REYIpWaLr4CKDMfCg==.sig.ed25519"},"timestamp":1573574678194,"rts":1439392020612}"#;
|
||||||
Feed::from_str(&feed)?;
|
Feed::from_slice(feed.as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
use super::error::Result;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use sodiumoxide::crypto::hash::sha256;
|
use sodiumoxide::crypto::hash::sha256;
|
||||||
use super::error::Result;
|
|
||||||
|
|
||||||
pub fn ssb_sha256(v: &Value) -> Result<sha256::Digest> {
|
pub fn ssb_sha256(v: &Value) -> Result<sha256::Digest> {
|
||||||
let v8encoding = stringify_json(&v)?
|
let v8encoding = stringify_json(&v)?
|
||||||
|
@ -60,8 +60,8 @@ pub fn stringify_json(v: &Value) -> Result<String> {
|
||||||
Value::Number(value) => {
|
Value::Number(value) => {
|
||||||
let mut as_str = value.to_string();
|
let mut as_str = value.to_string();
|
||||||
if as_str.contains('e') && !as_str.contains("e-") {
|
if as_str.contains('e') && !as_str.contains("e-") {
|
||||||
as_str = as_str.replace("e","e+")
|
as_str = as_str.replace("e", "e+")
|
||||||
}
|
}
|
||||||
buffer.push_str(&as_str);
|
buffer.push_str(&as_str);
|
||||||
}
|
}
|
||||||
Value::Bool(value) => {
|
Value::Bool(value) => {
|
||||||
|
@ -82,7 +82,7 @@ pub fn stringify_json(v: &Value) -> Result<String> {
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const JSON : &str = r#"{"a":0,"b":1.1,"c":null,"d":true,"f":false,"g":{},"h":{"h1":1},"i":[],"j":[1],"k":[1,2]}"#;
|
const JSON: &str = r#"{"a":0,"b":1.1,"c":null,"d":true,"f":false,"g":{},"h":{"h1":1},"i":[],"j":[1],"k":[1,2]}"#;
|
||||||
#[test]
|
#[test]
|
||||||
fn test_json_stringify() -> Result<()> {
|
fn test_json_stringify() -> Result<()> {
|
||||||
let v: Value = serde_json::from_str(JSON)?;
|
let v: Value = serde_json::from_str(JSON)?;
|
||||||
|
@ -124,8 +124,8 @@ mod test {
|
||||||
fn test_msg_with_float_mantissa() -> Result<()> {
|
fn test_msg_with_float_mantissa() -> Result<()> {
|
||||||
let expected = "RUcldndjJUkEcZ5hX6zAj/xLlnh0n4BZ6ThJOW5RvIk=";
|
let expected = "RUcldndjJUkEcZ5hX6zAj/xLlnh0n4BZ6ThJOW5RvIk=";
|
||||||
let message = r#"{"previous":"%gbem82xZNVHbOM2pyOlxymsAfstdMFfGSoawWQtObX8=.sha256","author":"@TXKFQehlyoSn8UJAIVP/k2BjFINC591MlBC2e2d24mA=.ed25519","sequence":1557,"timestamp":1495245157893,"hash":"sha256","content":{"type":"post","transactionHash":9.691449834862513e+76,"address":7.073631810716965e+46,"event":"ActionAdded","text":"{\"actionID\":\"1\",\"amount\":\"0\",\"description\":\"Bind Ethereum events to Secure Scuttlebutt posts\"}}"},"signature":"/Qvm9ozEfl0Thyvs+mnwhLDReZ8xeKXA3hSXOxm53SFkLEnnJ+IF0l7LSqc56Y3vl8FwarJ6k0PGmcU3U8FMAw==.sig.ed25519"}"#;
|
let message = r#"{"previous":"%gbem82xZNVHbOM2pyOlxymsAfstdMFfGSoawWQtObX8=.sha256","author":"@TXKFQehlyoSn8UJAIVP/k2BjFINC591MlBC2e2d24mA=.ed25519","sequence":1557,"timestamp":1495245157893,"hash":"sha256","content":{"type":"post","transactionHash":9.691449834862513e+76,"address":7.073631810716965e+46,"event":"ActionAdded","text":"{\"actionID\":\"1\",\"amount\":\"0\",\"description\":\"Bind Ethereum events to Secure Scuttlebutt posts\"}}"},"signature":"/Qvm9ozEfl0Thyvs+mnwhLDReZ8xeKXA3hSXOxm53SFkLEnnJ+IF0l7LSqc56Y3vl8FwarJ6k0PGmcU3U8FMAw==.sig.ed25519"}"#;
|
||||||
let message_value: Value = serde_json::from_str(&message)?;
|
let message_value: Value = serde_json::from_str(&message)?;
|
||||||
let current = base64::encode(&ssb_sha256(&message_value)?);
|
let current = base64::encode(&ssb_sha256(&message_value)?);
|
||||||
assert_eq!(expected, current);
|
assert_eq!(expected, current);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -134,10 +134,9 @@ mod test {
|
||||||
fn test_msg_with_float_precision() -> Result<()> {
|
fn test_msg_with_float_precision() -> Result<()> {
|
||||||
let expected = "BUtTVIJyN5fUXzQy2uQfCCzlAg0s6laQQqFIu+kGnFM=";
|
let expected = "BUtTVIJyN5fUXzQy2uQfCCzlAg0s6laQQqFIu+kGnFM=";
|
||||||
let message = r#"{"previous":"%ButTjV+H9VfONhX+lLbJb5LR+W14SFqbmjOfdMPZ5+4=.sha256","sequence":15034,"author":"@6ilZq3kN0F+dXFHAPjAwMm87JEb/VdB+LC9eIMW3sa0=.ed25519","timestamp":1567190273951.0159,"hash":"sha256","content":{"type":"vote","channel":null,"vote":{"link":"%GvtUsekEwsCj1cQ6+4Gihkm+ek99BhB537g1xUKjhsA=.sha256","value":1,"expression":"Like"}},"signature":"UkVfqDmBhHrDfMvFT8iUhEispAku/zbdXKCyRVlxYp2wNtJ4okwKE7hTkKhbiMVA7sGIV5dzHZyMotXCL46iDw==.sig.ed25519"}"#;
|
let message = r#"{"previous":"%ButTjV+H9VfONhX+lLbJb5LR+W14SFqbmjOfdMPZ5+4=.sha256","sequence":15034,"author":"@6ilZq3kN0F+dXFHAPjAwMm87JEb/VdB+LC9eIMW3sa0=.ed25519","timestamp":1567190273951.0159,"hash":"sha256","content":{"type":"vote","channel":null,"vote":{"link":"%GvtUsekEwsCj1cQ6+4Gihkm+ek99BhB537g1xUKjhsA=.sha256","value":1,"expression":"Like"}},"signature":"UkVfqDmBhHrDfMvFT8iUhEispAku/zbdXKCyRVlxYp2wNtJ4okwKE7hTkKhbiMVA7sGIV5dzHZyMotXCL46iDw==.sig.ed25519"}"#;
|
||||||
let message_value: Value = serde_json::from_str(&message)?;
|
let message_value: Value = serde_json::from_str(&message)?;
|
||||||
let current = base64::encode(&ssb_sha256(&message_value)?);
|
let current = base64::encode(&ssb_sha256(&message_value)?);
|
||||||
assert_eq!(expected, current);
|
assert_eq!(expected, current);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@ impl std::fmt::Display for Error {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{:?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::error::Error for Error { }
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
use std::time::SystemTime;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use sodiumoxide::crypto::{sign::ed25519};
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use sodiumoxide::crypto::sign::ed25519;
|
||||||
|
|
||||||
|
use super::error::{Error, Result};
|
||||||
|
use super::{ssb_sha256, stringify_json};
|
||||||
use crate::crypto::ToSodiumObject;
|
use crate::crypto::ToSodiumObject;
|
||||||
use crate::patchwork::IdentitySecret;
|
use crate::patchwork::IdentitySecret;
|
||||||
use super::{stringify_json,ssb_sha256};
|
|
||||||
use super::error::{Error,Result};
|
|
||||||
|
|
||||||
const MSG_PREVIOUS : &str = "previous";
|
const MSG_PREVIOUS: &str = "previous";
|
||||||
const MSG_AUTHOR : &str = "author";
|
const MSG_AUTHOR: &str = "author";
|
||||||
const MSG_SEQUENCE : &str = "sequence";
|
const MSG_SEQUENCE: &str = "sequence";
|
||||||
const MSG_TIMESTAMP : &str = "timestamp";
|
const MSG_TIMESTAMP: &str = "timestamp";
|
||||||
const MSG_HASH : &str = "hash";
|
const MSG_HASH: &str = "hash";
|
||||||
const MSG_CONTENT : &str = "content";
|
const MSG_CONTENT: &str = "content";
|
||||||
const MSG_SIGNATURE : &str = "signature";
|
const MSG_SIGNATURE: &str = "signature";
|
||||||
|
|
||||||
macro_rules! cast {
|
macro_rules! cast {
|
||||||
($input:expr,$pth:path) => {
|
($input:expr,$pth:path) => {
|
||||||
match $input {
|
match $input {
|
||||||
Some($pth(x)) => Ok(x),
|
Some($pth(x)) => Ok(x),
|
||||||
_ => Err(Error::InvalidJson),
|
_ => Err(Error::InvalidJson),
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! cast_opt {
|
macro_rules! cast_opt {
|
||||||
|
@ -32,88 +32,76 @@ macro_rules! cast_opt {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(Value::Null) => Ok(None),
|
Some(Value::Null) => Ok(None),
|
||||||
Some($pth(x)) => Ok(Some(x)),
|
Some($pth(x)) => Ok(Some(x)),
|
||||||
_ => Err(Error::InvalidJson)
|
_ => Err(Error::InvalidJson),
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
#[derive(Debug,Deserialize)]
|
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
value: serde_json::Value,
|
pub value: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
|
pub fn new(prev: Option<&Message>, identity: &IdentitySecret, content: Value) -> Result<Self> {
|
||||||
pub fn new(prev : Option<&Message>, identity: &IdentitySecret, content: Value) -> Result<Self> {
|
let mut value: serde_json::Map<String, Value> = serde_json::Map::new();
|
||||||
let mut value : serde_json::Map<String,Value> = serde_json::Map::new();
|
|
||||||
if let Some(prev) = prev {
|
if let Some(prev) = prev {
|
||||||
value.insert(MSG_PREVIOUS.to_string(), Value::String(prev.id()?));
|
value.insert(MSG_PREVIOUS.to_string(), Value::String(prev.id()?));
|
||||||
value.insert(
|
value.insert(
|
||||||
MSG_SEQUENCE.to_string(),
|
MSG_SEQUENCE.to_string(),
|
||||||
Value::Number(serde_json::Number::from(prev.sequence()+1))
|
Value::Number(serde_json::Number::from(prev.sequence() + 1)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
value.insert(
|
value.insert(
|
||||||
MSG_SEQUENCE.to_string(),
|
MSG_SEQUENCE.to_string(),
|
||||||
Value::Number(serde_json::Number::from(1))
|
Value::Number(serde_json::Number::from(1)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)?
|
||||||
.as_millis() as u64;
|
.as_millis() as u64;
|
||||||
|
|
||||||
let timestamp = Value::Number(serde_json::Number::from(timestamp));
|
let timestamp = Value::Number(serde_json::Number::from(timestamp));
|
||||||
|
|
||||||
value.insert(
|
value.insert(MSG_AUTHOR.to_string(), Value::String(identity.id.clone()));
|
||||||
MSG_AUTHOR.to_string(),
|
value.insert(MSG_TIMESTAMP.to_string(), timestamp);
|
||||||
Value::String(identity.id.clone())
|
value.insert(MSG_HASH.to_string(), Value::String("sha256".to_string()));
|
||||||
);
|
value.insert(MSG_CONTENT.to_string(), content);
|
||||||
value.insert(
|
|
||||||
MSG_TIMESTAMP.to_string(),
|
|
||||||
timestamp
|
|
||||||
);
|
|
||||||
value.insert(
|
|
||||||
MSG_HASH.to_string(),
|
|
||||||
Value::String("sha256".to_string())
|
|
||||||
);
|
|
||||||
value.insert(
|
|
||||||
MSG_CONTENT.to_string(),
|
|
||||||
content
|
|
||||||
);
|
|
||||||
|
|
||||||
let value = Value::Object(value);
|
let value = Value::Object(value);
|
||||||
let to_sign_text = stringify_json(&value)?;
|
let to_sign_text = stringify_json(&value)?;
|
||||||
let mut value = cast!(Some(value),Value::Object)?;
|
let mut value = cast!(Some(value), Value::Object)?;
|
||||||
|
|
||||||
let signature = ed25519::sign_detached(to_sign_text.as_bytes(), &identity.sk);
|
let signature = ed25519::sign_detached(to_sign_text.as_bytes(), &identity.sk);
|
||||||
value.insert(
|
value.insert(
|
||||||
MSG_SIGNATURE.to_string(),
|
MSG_SIGNATURE.to_string(),
|
||||||
Value::String(format!("{}.sig.ed25519",base64::encode(&signature)))
|
Value::String(format!("{}.sig.ed25519", base64::encode(&signature))),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(Message { value : Value::Object(value) })
|
Ok(Message {
|
||||||
|
value: Value::Object(value),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_slice(s : &[u8]) -> Result<Self> {
|
pub fn from_slice(s: &[u8]) -> Result<Self> {
|
||||||
Self::from_value(serde_json::from_slice(&s)?)
|
Self::from_value(serde_json::from_slice(&s)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_value(v : Value) -> Result<Self> {
|
pub fn from_value(v: Value) -> Result<Self> {
|
||||||
|
let mut v = cast!(Some(v), Value::Object)?;
|
||||||
|
|
||||||
let mut v = cast!(Some(v),Value::Object)?;
|
// check if ok
|
||||||
|
cast_opt!(v.get(MSG_PREVIOUS), Value::String)?;
|
||||||
// check if ok
|
cast!(v.get(MSG_SEQUENCE), Value::Number)?;
|
||||||
cast_opt!(v.get(MSG_PREVIOUS),Value::String)?;
|
cast!(v.get(MSG_SEQUENCE), Value::Number)?;
|
||||||
cast!(v.get(MSG_SEQUENCE),Value::Number)?;
|
cast!(v.get(MSG_TIMESTAMP), Value::Number)?;
|
||||||
cast!(v.get(MSG_SEQUENCE),Value::Number)?;
|
cast!(v.get(MSG_HASH), Value::String)?;
|
||||||
cast!(v.get(MSG_TIMESTAMP),Value::Number)?;
|
|
||||||
cast!(v.get(MSG_HASH),Value::String)?;
|
|
||||||
v.get(MSG_CONTENT).ok_or(Error::InvalidJson)?;
|
v.get(MSG_CONTENT).ok_or(Error::InvalidJson)?;
|
||||||
|
|
||||||
// verify signature
|
// verify signature
|
||||||
let signature = cast!(v.remove(MSG_SIGNATURE),Value::String)?;
|
let signature = cast!(v.remove(MSG_SIGNATURE), Value::String)?;
|
||||||
let author = cast!(v.get(MSG_AUTHOR),Value::String)?;
|
let author = cast!(v.get(MSG_AUTHOR), Value::String)?;
|
||||||
let sig = signature.to_ed25519_signature()?;
|
let sig = signature.to_ed25519_signature()?;
|
||||||
let signer = author[1..].to_ed25519_pk()?;
|
let signer = author[1..].to_ed25519_pk()?;
|
||||||
|
|
||||||
|
@ -122,42 +110,44 @@ impl Message {
|
||||||
if !ed25519::verify_detached(&sig, &signed_text.as_ref(), &signer) {
|
if !ed25519::verify_detached(&sig, &signed_text.as_ref(), &signer) {
|
||||||
return Err(Error::InvalidSignature);
|
return Err(Error::InvalidSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
// put signature back
|
// put signature back
|
||||||
let mut v = cast!(Some(value),Value::Object)?;
|
let mut v = cast!(Some(value), Value::Object)?;
|
||||||
v.insert(MSG_SIGNATURE.to_string(), Value::String(signature));
|
v.insert(MSG_SIGNATURE.to_string(), Value::String(signature));
|
||||||
|
|
||||||
Ok(Message { value : Value::Object(v) })
|
Ok(Message {
|
||||||
|
value: Value::Object(v),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> Result<String> {
|
pub fn id(&self) -> Result<String> {
|
||||||
let digest = base64::encode(&ssb_sha256(&self.value)?);
|
let digest = base64::encode(&ssb_sha256(&self.value)?);
|
||||||
Ok(format!("%{}.sha256",digest))
|
Ok(format!("%{}.sha256", digest))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previous(&self) -> Option<&String> {
|
pub fn previous(&self) -> Option<&String> {
|
||||||
cast_opt!(self.value.get(MSG_PREVIOUS),Value::String)
|
cast_opt!(self.value.get(MSG_PREVIOUS), Value::String).unwrap()
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
pub fn author(&self) -> &String {
|
pub fn author(&self) -> &String {
|
||||||
cast!(self.value.get(MSG_AUTHOR),Value::String)
|
cast!(self.value.get(MSG_AUTHOR), Value::String).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sequence(&self) -> u64 {
|
||||||
|
cast!(self.value.get(MSG_SEQUENCE), Value::Number)
|
||||||
|
.unwrap()
|
||||||
|
.as_u64()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sequence(&self) -> u64 {
|
|
||||||
cast!(self.value.get(MSG_SEQUENCE),Value::Number)
|
|
||||||
.unwrap().as_u64().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn timestamp(&self) -> f64 {
|
pub fn timestamp(&self) -> f64 {
|
||||||
cast!(self.value.get(MSG_TIMESTAMP),Value::Number)
|
cast!(self.value.get(MSG_TIMESTAMP), Value::Number)
|
||||||
.unwrap().as_f64().unwrap()
|
.unwrap()
|
||||||
|
.as_f64()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash(&self) -> &String {
|
pub fn hash(&self) -> &String {
|
||||||
cast!(self.value.get(MSG_HASH),Value::String)
|
cast!(self.value.get(MSG_HASH), Value::String).unwrap()
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(&self) -> &Value {
|
pub fn content(&self) -> &Value {
|
||||||
|
@ -165,10 +155,8 @@ impl Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn signature(&self) -> &String {
|
pub fn signature(&self) -> &String {
|
||||||
cast!(self.value.get(MSG_SIGNATURE),Value::String)
|
cast!(self.value.get(MSG_SIGNATURE), Value::String).unwrap()
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Message {
|
impl FromStr for Message {
|
||||||
|
@ -188,13 +176,13 @@ impl ToString for Message {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_verify_known_msg_integrity() -> Result<()> {
|
fn test_verify_known_msg_integrity() -> Result<()> {
|
||||||
let message_id ="%Cg0ZpZ8cV85G8UIIropgBOvM8+Srlv9LSGDNGnpdK44=.sha256";
|
let message_id = "%Cg0ZpZ8cV85G8UIIropgBOvM8+Srlv9LSGDNGnpdK44=.sha256";
|
||||||
let message = r#"{"previous":"%seUEAo7PTyA7vNwnOrmGIsUFfpyRzOvzGVv1QCb/Fz8=.sha256","author":"@BIbVppzlrNiRJogxDYz3glUS7G4s4D4NiXiPEAEzxdE=.ed25519","sequence":37,"timestamp":1439392020612,"hash":"sha256","content":{"type":"post","text":"@paul real time replies didn't work.","repliesTo":"%xWKunF6nXD7XMC+D4cjwDMZWmBnmRu69w9T25iLNa1Q=.sha256","mentions":["%7UKRfZb2u8al4tYWHqM55R9xpE/KKVh9U0M6BdugGt4=.sha256"],"recps":[{"link":"@hxGxqPrplLjRG2vtjQL87abX4QKqeLgCwQpS730nNwE=.ed25519","name":"paul"}]},"signature":"gGxSPdBJZxp6x5f3HzQGoQSeSdh/C5AtymIn+miWa+lcC6DdqpRSgaeH9KHeLf+/CKhU6REYIpWaLr4CKDMfCg==.sig.ed25519"}"#;
|
let message = r#"{"previous":"%seUEAo7PTyA7vNwnOrmGIsUFfpyRzOvzGVv1QCb/Fz8=.sha256","author":"@BIbVppzlrNiRJogxDYz3glUS7G4s4D4NiXiPEAEzxdE=.ed25519","sequence":37,"timestamp":1439392020612,"hash":"sha256","content":{"type":"post","text":"@paul real time replies didn't work.","repliesTo":"%xWKunF6nXD7XMC+D4cjwDMZWmBnmRu69w9T25iLNa1Q=.sha256","mentions":["%7UKRfZb2u8al4tYWHqM55R9xpE/KKVh9U0M6BdugGt4=.sha256"],"recps":[{"link":"@hxGxqPrplLjRG2vtjQL87abX4QKqeLgCwQpS730nNwE=.ed25519","name":"paul"}]},"signature":"gGxSPdBJZxp6x5f3HzQGoQSeSdh/C5AtymIn+miWa+lcC6DdqpRSgaeH9KHeLf+/CKhU6REYIpWaLr4CKDMfCg==.sig.ed25519"}"#;
|
||||||
let msg = Message::from_str(&message)?;
|
let msg = Message::from_str(&message)?;
|
||||||
assert_eq!(msg.id()?,message_id);
|
assert_eq!(msg.id()?, message_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,11 +190,10 @@ mod test {
|
||||||
fn test_sign_verify() -> Result<()> {
|
fn test_sign_verify() -> Result<()> {
|
||||||
let content = Value::String("thistest".to_string());
|
let content = Value::String("thistest".to_string());
|
||||||
let id = IdentitySecret::new();
|
let id = IdentitySecret::new();
|
||||||
let msg1 = Message::new(None,&id,content.clone())?.to_string();
|
let msg1 = Message::new(None, &id, content.clone())?.to_string();
|
||||||
let msg1 = Message::from_str(&msg1)?;
|
let msg1 = Message::from_str(&msg1)?;
|
||||||
let msg2 = Message::new(Some(&msg1),&id,content)?.to_string();
|
let msg2 = Message::new(Some(&msg1), &id, content)?.to_string();
|
||||||
Message::from_str(&msg2)?;
|
Message::from_str(&msg2)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
mod encoding;
|
|
||||||
mod base;
|
mod base;
|
||||||
|
mod encoding;
|
||||||
|
mod error;
|
||||||
mod message;
|
mod message;
|
||||||
mod privatebox;
|
mod privatebox;
|
||||||
mod error;
|
|
||||||
|
|
||||||
pub use message::Message;
|
|
||||||
pub use base::Feed;
|
pub use base::Feed;
|
||||||
pub use privatebox::{is_privatebox,privatebox_cipher,privatebox_decipher};
|
pub use encoding::{ssb_sha256, stringify_json};
|
||||||
pub use encoding::{ssb_sha256,stringify_json};
|
pub use error::{Error, Result};
|
||||||
pub use error::{Error,Result};
|
pub use message::Message;
|
||||||
|
pub use privatebox::{is_privatebox, privatebox_cipher, privatebox_decipher};
|
||||||
|
|
|
@ -1,58 +1,52 @@
|
||||||
use crate::crypto::ToSodiumObject;
|
use crate::crypto::ToSodiumObject;
|
||||||
|
|
||||||
use sodiumoxide::crypto::sign::SecretKey;
|
use sodiumoxide::crypto::sign::SecretKey;
|
||||||
use sodiumoxide::crypto::{sign::ed25519, scalarmult::curve25519, secretbox};
|
use sodiumoxide::crypto::{scalarmult::curve25519, secretbox, sign::ed25519};
|
||||||
|
|
||||||
use super::error::{Error,Result};
|
use super::error::{Error, Result};
|
||||||
|
|
||||||
pub const SUFFIX : &str = ".box";
|
pub const SUFFIX: &str = ".box";
|
||||||
|
|
||||||
pub const MAX_RECIPIENTS : u8 = 7;
|
pub const MAX_RECIPIENTS: u8 = 7;
|
||||||
|
|
||||||
const RECIPIENT_COUNT_LEN : usize = 1;
|
const RECIPIENT_COUNT_LEN: usize = 1;
|
||||||
const ENCRYPTED_HEADER_LEN : usize =
|
const ENCRYPTED_HEADER_LEN: usize = RECIPIENT_COUNT_LEN + secretbox::KEYBYTES + secretbox::MACBYTES;
|
||||||
RECIPIENT_COUNT_LEN
|
|
||||||
+ secretbox::KEYBYTES
|
|
||||||
+ secretbox::MACBYTES;
|
|
||||||
|
|
||||||
pub fn is_privatebox(text : &str) -> bool {
|
pub fn is_privatebox(text: &str) -> bool {
|
||||||
text.ends_with(SUFFIX)
|
text.ends_with(SUFFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn privatebox_cipher(plaintext : &str, recipients: &[&str]) -> Result<String> {
|
pub fn privatebox_cipher(plaintext: &str, recipients: &[&str]) -> Result<String> {
|
||||||
let recipients : crate::crypto::Result<Vec<_>> = recipients
|
let recipients: crate::crypto::Result<Vec<_>> = recipients
|
||||||
.iter()
|
.iter()
|
||||||
.map(|id| {
|
.map(|id| id[1..].to_ed25519_pk())
|
||||||
id[1..].to_ed25519_pk()
|
.collect();
|
||||||
}).collect();
|
|
||||||
|
|
||||||
let recipients = recipients?;
|
let recipients = recipients?;
|
||||||
|
|
||||||
let recipients_ref : Vec<_> =
|
let recipients_ref: Vec<_> = recipients.iter().map(|r| r).collect();
|
||||||
recipients.iter().map(|r| r).collect();
|
|
||||||
|
|
||||||
let ciphertext = cipher(plaintext.as_bytes(),&recipients_ref[..])?;
|
let ciphertext = cipher(plaintext.as_bytes(), &recipients_ref[..])?;
|
||||||
|
|
||||||
Ok(format!("{}{}",base64::encode(&ciphertext),SUFFIX))
|
Ok(format!("{}{}", base64::encode(&ciphertext), SUFFIX))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn privatebox_decipher(ciphertext : &str, sk : &SecretKey) -> Result<Option<String>> {
|
pub fn privatebox_decipher(ciphertext: &str, sk: &SecretKey) -> Result<Option<String>> {
|
||||||
let msg = &ciphertext.as_bytes()[..ciphertext.len()-SUFFIX.len()];
|
let msg = &ciphertext.as_bytes()[..ciphertext.len() - SUFFIX.len()];
|
||||||
let msg = base64::decode(msg)?;
|
let msg = base64::decode(msg)?;
|
||||||
|
|
||||||
let plaintext = decipher(&msg, &sk)?
|
let plaintext = decipher(&msg, &sk)?.map(|msg| String::from_utf8_lossy(&msg).to_string());
|
||||||
.map(|msg| String::from_utf8_lossy(&msg).to_string());
|
|
||||||
|
|
||||||
Ok(plaintext)
|
Ok(plaintext)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cipher(plaintext : &[u8], recipients : &[&ed25519::PublicKey]) -> Result<Box<[u8]>> {
|
fn cipher(plaintext: &[u8], recipients: &[&ed25519::PublicKey]) -> Result<Box<[u8]>> {
|
||||||
// Precondition checks
|
// Precondition checks
|
||||||
if plaintext.is_empty() {
|
if plaintext.is_empty() {
|
||||||
return Err(Error::EmptyPlaintext);
|
return Err(Error::EmptyPlaintext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if recipients.is_empty() || recipients.len() > MAX_RECIPIENTS as usize{
|
if recipients.is_empty() || recipients.len() > MAX_RECIPIENTS as usize {
|
||||||
return Err(Error::BadRecipientCount);
|
return Err(Error::BadRecipientCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +55,7 @@ fn cipher(plaintext : &[u8], recipients : &[&ed25519::PublicKey]) -> Result<Box<
|
||||||
|
|
||||||
// Generated random 32-byte secret key used to encrypt the message body
|
// Generated random 32-byte secret key used to encrypt the message body
|
||||||
let y = sodiumoxide::crypto::secretbox::gen_key();
|
let y = sodiumoxide::crypto::secretbox::gen_key();
|
||||||
|
|
||||||
// Encrypt the plaintext with y, with a random nonce
|
// Encrypt the plaintext with y, with a random nonce
|
||||||
let nonce = secretbox::gen_nonce();
|
let nonce = secretbox::gen_nonce();
|
||||||
let cipher_message = secretbox::seal(plaintext, &nonce, &y);
|
let cipher_message = secretbox::seal(plaintext, &nonce, &y);
|
||||||
|
@ -69,15 +63,16 @@ fn cipher(plaintext : &[u8], recipients : &[&ed25519::PublicKey]) -> Result<Box<
|
||||||
// The sender uses scalar multiplication to derive a shared secret for each recipient,
|
// The sender uses scalar multiplication to derive a shared secret for each recipient,
|
||||||
// and encrypts (number_of_recipents || y) for each one
|
// and encrypts (number_of_recipents || y) for each one
|
||||||
let h_sk_scalar = &h_sk.to_curve25519();
|
let h_sk_scalar = &h_sk.to_curve25519();
|
||||||
let mut plain_header = [0u8;RECIPIENT_COUNT_LEN+secretbox::KEYBYTES];
|
let mut plain_header = [0u8; RECIPIENT_COUNT_LEN + secretbox::KEYBYTES];
|
||||||
plain_header[0]=recipients.len() as u8;
|
plain_header[0] = recipients.len() as u8;
|
||||||
plain_header[RECIPIENT_COUNT_LEN..].copy_from_slice(&y[..]);
|
plain_header[RECIPIENT_COUNT_LEN..].copy_from_slice(&y[..]);
|
||||||
|
|
||||||
let mut buffer : Vec<u8> = Vec::with_capacity(
|
let mut buffer: Vec<u8> = Vec::with_capacity(
|
||||||
secretbox::NONCEBYTES
|
secretbox::NONCEBYTES
|
||||||
+ ed25519::PUBLICKEYBYTES
|
+ ed25519::PUBLICKEYBYTES
|
||||||
+ ENCRYPTED_HEADER_LEN*recipients.len()
|
+ ENCRYPTED_HEADER_LEN * recipients.len()
|
||||||
+ secretbox::MACBYTES+plaintext.len()
|
+ secretbox::MACBYTES
|
||||||
|
+ plaintext.len(),
|
||||||
);
|
);
|
||||||
|
|
||||||
buffer.extend_from_slice(&nonce[..]);
|
buffer.extend_from_slice(&nonce[..]);
|
||||||
|
@ -87,20 +82,18 @@ fn cipher(plaintext : &[u8], recipients : &[&ed25519::PublicKey]) -> Result<Box<
|
||||||
let key = curve25519::scalarmult(&h_sk_scalar, &recipient.to_curve25519())
|
let key = curve25519::scalarmult(&h_sk_scalar, &recipient.to_curve25519())
|
||||||
.map_err(|_| Error::CryptoScalarMultFailed)?;
|
.map_err(|_| Error::CryptoScalarMultFailed)?;
|
||||||
|
|
||||||
let key = secretbox::Key::from_slice(&key[..])
|
let key = secretbox::Key::from_slice(&key[..]).ok_or(Error::CryptoKeyFromGrupFailed)?;
|
||||||
.ok_or(Error::CryptoKeyFromGrupFailed)?;
|
|
||||||
|
|
||||||
buffer.extend_from_slice(&secretbox::seal(&plain_header[..],&nonce, &key));
|
buffer.extend_from_slice(&secretbox::seal(&plain_header[..], &nonce, &key));
|
||||||
}
|
}
|
||||||
buffer.extend_from_slice(&cipher_message[..]);
|
buffer.extend_from_slice(&cipher_message[..]);
|
||||||
|
|
||||||
Ok(buffer.into_boxed_slice())
|
Ok(buffer.into_boxed_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decipher(ciphertext : &[u8], sk : &SecretKey) -> Result<Option<Vec<u8>>> {
|
fn decipher(ciphertext: &[u8], sk: &SecretKey) -> Result<Option<Vec<u8>>> {
|
||||||
|
|
||||||
let mut cursor = ciphertext;
|
let mut cursor = ciphertext;
|
||||||
|
|
||||||
let nonce = secretbox::Nonce::from_slice(&cursor[..secretbox::NONCEBYTES])
|
let nonce = secretbox::Nonce::from_slice(&cursor[..secretbox::NONCEBYTES])
|
||||||
.ok_or(Error::CannotReadNonce)?;
|
.ok_or(Error::CannotReadNonce)?;
|
||||||
cursor = &cursor[secretbox::NONCEBYTES..];
|
cursor = &cursor[secretbox::NONCEBYTES..];
|
||||||
|
@ -108,17 +101,16 @@ fn decipher(ciphertext : &[u8], sk : &SecretKey) -> Result<Option<Vec<u8>>> {
|
||||||
let h_pk = curve25519::GroupElement::from_slice(&cursor[..ed25519::PUBLICKEYBYTES])
|
let h_pk = curve25519::GroupElement::from_slice(&cursor[..ed25519::PUBLICKEYBYTES])
|
||||||
.ok_or(Error::CannotReadNonce)?;
|
.ok_or(Error::CannotReadNonce)?;
|
||||||
cursor = &cursor[ed25519::PUBLICKEYBYTES..];
|
cursor = &cursor[ed25519::PUBLICKEYBYTES..];
|
||||||
|
|
||||||
let key = curve25519::scalarmult(&sk.to_curve25519(), &h_pk)
|
let key = curve25519::scalarmult(&sk.to_curve25519(), &h_pk)
|
||||||
.and_then(|key| secretbox::Key::from_slice(&key[..]).ok_or(()))
|
.and_then(|key| secretbox::Key::from_slice(&key[..]).ok_or(()))
|
||||||
.map_err(|_| Error::CannotCreateKey)?;
|
.map_err(|_| Error::CannotCreateKey)?;
|
||||||
|
|
||||||
let mut header_no = 0;
|
let mut header_no = 0;
|
||||||
while header_no < MAX_RECIPIENTS && cursor.len() > ENCRYPTED_HEADER_LEN+secretbox::MACBYTES {
|
while header_no < MAX_RECIPIENTS && cursor.len() > ENCRYPTED_HEADER_LEN + secretbox::MACBYTES {
|
||||||
if let Ok(header) = secretbox::open(&cursor[..ENCRYPTED_HEADER_LEN], &nonce, &key) {
|
if let Ok(header) = secretbox::open(&cursor[..ENCRYPTED_HEADER_LEN], &nonce, &key) {
|
||||||
let encrypted_message_offset = ENCRYPTED_HEADER_LEN*(header[0]-header_no) as usize;
|
let encrypted_message_offset = ENCRYPTED_HEADER_LEN * (header[0] - header_no) as usize;
|
||||||
let y = secretbox::Key::from_slice(&header[1..])
|
let y = secretbox::Key::from_slice(&header[1..]).ok_or(Error::CannotCreateKey)?;
|
||||||
.ok_or(Error::CannotCreateKey)?;
|
|
||||||
let plaintext = secretbox::open(&cursor[encrypted_message_offset..], &nonce, &y)
|
let plaintext = secretbox::open(&cursor[encrypted_message_offset..], &nonce, &y)
|
||||||
.map_err(|_| Error::FailedToDecipher)?;
|
.map_err(|_| Error::FailedToDecipher)?;
|
||||||
return Ok(Some(plaintext));
|
return Ok(Some(plaintext));
|
||||||
|
@ -134,14 +126,14 @@ fn decipher(ciphertext : &[u8], sk : &SecretKey) -> Result<Option<Vec<u8>>> {
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::patchwork::IdentitySecret;
|
use crate::patchwork::IdentitySecret;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_msg_cipher_to_one() -> Result<()> {
|
fn test_msg_cipher_to_one() -> Result<()> {
|
||||||
let (u1_pk, u1_sk) = ed25519::gen_keypair();
|
let (u1_pk, u1_sk) = ed25519::gen_keypair();
|
||||||
let plaintext = "hola".as_bytes();
|
let plaintext = "hola".as_bytes();
|
||||||
let ciphertext = cipher(plaintext, &[&u1_pk])?;
|
let ciphertext = cipher(plaintext, &[&u1_pk])?;
|
||||||
let plaintext_1 = decipher(&ciphertext, &u1_sk)?.unwrap();
|
let plaintext_1 = decipher(&ciphertext, &u1_sk)?.unwrap();
|
||||||
assert_eq!(plaintext.to_vec(),plaintext_1);
|
assert_eq!(plaintext.to_vec(), plaintext_1);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,9 +142,9 @@ mod test {
|
||||||
let id = IdentitySecret::new();
|
let id = IdentitySecret::new();
|
||||||
let plaintext = "holar";
|
let plaintext = "holar";
|
||||||
let ciphertext = privatebox_cipher(plaintext, &[&id.id])?;
|
let ciphertext = privatebox_cipher(plaintext, &[&id.id])?;
|
||||||
assert_eq!(is_privatebox(&ciphertext),true);
|
assert_eq!(is_privatebox(&ciphertext), true);
|
||||||
let plaintext_1 = privatebox_decipher(&ciphertext, &id.sk)?.unwrap();
|
let plaintext_1 = privatebox_decipher(&ciphertext, &id.sk)?.unwrap();
|
||||||
assert_eq!(plaintext,plaintext_1);
|
assert_eq!(plaintext, plaintext_1);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,21 +155,21 @@ mod test {
|
||||||
let plaintext = "hola".as_bytes();
|
let plaintext = "hola".as_bytes();
|
||||||
let ciphertext = cipher(plaintext, &[&u1_pk])?;
|
let ciphertext = cipher(plaintext, &[&u1_pk])?;
|
||||||
let plaintext_1 = decipher(&ciphertext, &u1_sk)?;
|
let plaintext_1 = decipher(&ciphertext, &u1_sk)?;
|
||||||
assert_eq!(None,plaintext_1);
|
assert_eq!(None, plaintext_1);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_msg_cipher_to_multiple() -> Result<()> {
|
fn test_msg_cipher_to_multiple() -> Result<()> {
|
||||||
let u = (0..7).map(|_| ed25519::gen_keypair()).collect::<Vec<_>>();
|
let u = (0..7).map(|_| ed25519::gen_keypair()).collect::<Vec<_>>();
|
||||||
let u_pk = u.iter().map(|(pk,_)| pk).collect::<Vec<_>>();
|
let u_pk = u.iter().map(|(pk, _)| pk).collect::<Vec<_>>();
|
||||||
|
|
||||||
let plaintext = "hola".as_bytes();
|
let plaintext = "hola".as_bytes();
|
||||||
let ciphertext = cipher(plaintext, &u_pk)?;
|
let ciphertext = cipher(plaintext, &u_pk)?;
|
||||||
|
|
||||||
for (_,sk) in u.iter() {
|
for (_, sk) in u.iter() {
|
||||||
let plaintext_1 = decipher(&ciphertext, sk)?.unwrap();
|
let plaintext_1 = decipher(&ciphertext, sk)?.unwrap();
|
||||||
assert_eq!(plaintext.to_vec(),plaintext_1);
|
assert_eq!(plaintext.to_vec(), plaintext_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -2,11 +2,11 @@ extern crate kuska_handshake;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
|
||||||
extern crate async_std;
|
extern crate async_std;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
pub mod rpc;
|
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod patchwork;
|
|
||||||
pub mod feed;
|
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
pub mod feed;
|
||||||
|
pub mod patchwork;
|
||||||
|
pub mod rpc;
|
||||||
|
|
|
@ -1,30 +1,27 @@
|
||||||
use async_std::io::{Read, Write};
|
use async_std::io::{Read, Write};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::rpc::{RpcStream, RequestNo, RpcType};
|
use crate::rpc::{BodyType, RequestNo, RpcStream, RpcType};
|
||||||
use crate::feed::Message;
|
|
||||||
use crate::feed::Feed;
|
|
||||||
|
|
||||||
use super::error::Result;
|
use super::error::Result;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct ErrorRes {
|
pub struct ErrorRes {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub stack: String,
|
pub stack: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct WhoAmI {
|
pub struct WhoAmI {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct LatestUserMessage {
|
pub struct LatestUserMessage {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub sequence: u64,
|
pub sequence: u64,
|
||||||
pub ts: u64,
|
pub ts: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/ssbc/ssb-db/blob/master/api.md
|
// https://github.com/ssbc/ssb-db/blob/master/api.md
|
||||||
|
@ -154,10 +151,10 @@ impl<K> CreateStreamArgs<K> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct CreateHistoryStreamArgs<'a> {
|
pub struct CreateHistoryStreamArgs {
|
||||||
// id (FeedID, required): The id of the feed to fetch.
|
// id (FeedID, required): The id of the feed to fetch.
|
||||||
pub id: &'a str,
|
pub id: String,
|
||||||
|
|
||||||
/// (number, default: 0): If seq > 0, then only stream messages with sequence numbers greater than seq.
|
/// (number, default: 0): If seq > 0, then only stream messages with sequence numbers greater than seq.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
@ -179,8 +176,8 @@ pub struct CreateHistoryStreamArgs<'a> {
|
||||||
pub limit: Option<u64>,
|
pub limit: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CreateHistoryStreamArgs<'a> {
|
impl CreateHistoryStreamArgs {
|
||||||
pub fn new(id: &'a str) -> Self {
|
pub fn new(id: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
seq: None,
|
seq: None,
|
||||||
|
@ -217,27 +214,44 @@ impl<'a> CreateHistoryStreamArgs<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_whoami(body: &[u8]) -> Result<WhoAmI> {
|
#[derive(Debug)]
|
||||||
Ok(serde_json::from_slice(body)?)
|
pub enum ApiMethod {
|
||||||
|
WhoAmI,
|
||||||
|
Get,
|
||||||
|
CreateHistoryStream,
|
||||||
|
CreateFeedStream,
|
||||||
|
Latest,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_message(body: &[u8]) -> Result<Message> {
|
impl ApiMethod {
|
||||||
Ok(serde_json::from_slice(body)?)
|
pub fn selector(&self) -> &'static [&'static str] {
|
||||||
|
use ApiMethod::*;
|
||||||
|
match self {
|
||||||
|
WhoAmI => &["whoami"],
|
||||||
|
Get => &["get"],
|
||||||
|
CreateHistoryStream => &["createHistoryStream"],
|
||||||
|
CreateFeedStream => &["createFeedStream"],
|
||||||
|
Latest => &["latest"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_selector(s: &[&str]) -> Option<Self> {
|
||||||
|
use ApiMethod::*;
|
||||||
|
match s {
|
||||||
|
["whoami"] => Some(WhoAmI),
|
||||||
|
["get"] => Some(Get),
|
||||||
|
["createHistoryStream"] => Some(CreateHistoryStream),
|
||||||
|
["createFeedStream"] => Some(CreateFeedStream),
|
||||||
|
["latest"] => Some(Latest),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_feed(body: &[u8]) -> Result<Feed> {
|
pub struct ApiHelper<R: Read + Unpin, W: Write + Unpin> {
|
||||||
Ok(Feed::from_str(&String::from_utf8_lossy(body))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_latest(body: &[u8]) -> Result<LatestUserMessage> {
|
|
||||||
Ok(serde_json::from_slice(body)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ApiClient<R: Read + Unpin, W: Write + Unpin> {
|
|
||||||
rpc: RpcStream<R, W>,
|
rpc: RpcStream<R, W>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read + Unpin, W: Write + Unpin> ApiClient<R, W> {
|
impl<R: Read + Unpin, W: Write + Unpin> ApiHelper<R, W> {
|
||||||
pub fn new(rpc: RpcStream<R, W>) -> Self {
|
pub fn new(rpc: RpcStream<R, W>) -> Self {
|
||||||
Self { rpc }
|
Self { rpc }
|
||||||
}
|
}
|
||||||
|
@ -248,40 +262,68 @@ impl<R: Read + Unpin, W: Write + Unpin> ApiClient<R, W> {
|
||||||
|
|
||||||
// whoami: sync
|
// whoami: sync
|
||||||
// Get information about the current ssb-server user.
|
// Get information about the current ssb-server user.
|
||||||
pub async fn send_whoami(&mut self) -> Result<RequestNo> {
|
pub async fn whoami_req_send(&mut self) -> Result<RequestNo> {
|
||||||
let args: [&str; 0] = [];
|
let args: [&str; 0] = [];
|
||||||
let req_no = self.rpc.send_request(&["whoami"], RpcType::Async, &args).await?;
|
let req_no = self
|
||||||
|
.rpc
|
||||||
|
.send_request(ApiMethod::WhoAmI.selector(), RpcType::Async, &args)
|
||||||
|
.await?;
|
||||||
Ok(req_no)
|
Ok(req_no)
|
||||||
}
|
}
|
||||||
|
pub async fn whoami_res_send(&mut self, req_no: RequestNo, id: String) -> Result<()> {
|
||||||
|
let body = serde_json::to_string(&WhoAmI { id })?;
|
||||||
|
Ok(self
|
||||||
|
.rpc
|
||||||
|
.send_response(req_no, RpcType::Async, BodyType::JSON, body.as_bytes())
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
// get: async
|
// get: async
|
||||||
// Get a message by its hash-id. (sould start with %)
|
// Get a message by its hash-id. (sould start with %)
|
||||||
pub async fn send_get(&mut self, msg_id: &str) -> Result<RequestNo> {
|
pub async fn get_req_send(&mut self, msg_id: &str) -> Result<RequestNo> {
|
||||||
let req_no = self.rpc.send_request(&["get"], RpcType::Async, &msg_id).await?;
|
let req_no = self
|
||||||
|
.rpc
|
||||||
|
.send_request(ApiMethod::Get.selector(), RpcType::Async, &msg_id)
|
||||||
|
.await?;
|
||||||
Ok(req_no)
|
Ok(req_no)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createHistoryStream: source
|
// createHistoryStream: source
|
||||||
// (hist) Fetch messages from a specific user, ordered by sequence numbers.
|
// (hist) Fetch messages from a specific user, ordered by sequence numbers.
|
||||||
pub async fn send_create_history_stream<'a>(
|
pub async fn create_history_stream_req_send(
|
||||||
&mut self,
|
&mut self,
|
||||||
args: &'a CreateHistoryStreamArgs<'a>,
|
args: &CreateHistoryStreamArgs,
|
||||||
) -> Result<RequestNo> {
|
) -> Result<RequestNo> {
|
||||||
let req_no = self
|
let req_no = self
|
||||||
.rpc
|
.rpc
|
||||||
.send_request(&["createHistoryStream"], RpcType::Source, &args)
|
.send_request(
|
||||||
|
ApiMethod::CreateHistoryStream.selector(),
|
||||||
|
RpcType::Source,
|
||||||
|
&args,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(req_no)
|
Ok(req_no)
|
||||||
}
|
}
|
||||||
|
pub async fn feed_res_send(&mut self, req_no: RequestNo, feed: &str) -> Result<()> {
|
||||||
|
self.rpc
|
||||||
|
.send_response(req_no, RpcType::Async, BodyType::JSON, feed.as_bytes())
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// createFeedStream: source
|
// createFeedStream: source
|
||||||
// (feed) Fetch messages ordered by their claimed timestamps.
|
// (feed) Fetch messages ordered by their claimed timestamps.
|
||||||
pub async fn send_create_feed_stream<'a>(
|
pub async fn send_create_feed_stream<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
args: &'a CreateStreamArgs<u64>,
|
args: &CreateStreamArgs<u64>,
|
||||||
) -> Result<RequestNo> {
|
) -> Result<RequestNo> {
|
||||||
let req_no = self
|
let req_no = self
|
||||||
.rpc
|
.rpc
|
||||||
.send_request(&["createFeedStream"], RpcType::Source, &args)
|
.send_request(
|
||||||
|
ApiMethod::CreateFeedStream.selector(),
|
||||||
|
RpcType::Source,
|
||||||
|
&args,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(req_no)
|
Ok(req_no)
|
||||||
}
|
}
|
||||||
|
@ -290,8 +332,10 @@ impl<R: Read + Unpin, W: Write + Unpin> ApiClient<R, W> {
|
||||||
// Get the seq numbers of the latest messages of all users in the database.
|
// Get the seq numbers of the latest messages of all users in the database.
|
||||||
pub async fn send_latest(&mut self) -> Result<RequestNo> {
|
pub async fn send_latest(&mut self) -> Result<RequestNo> {
|
||||||
let args: [&str; 0] = [];
|
let args: [&str; 0] = [];
|
||||||
let req_no = self.rpc.send_request(&["latest"], RpcType::Source, &args).await?;
|
let req_no = self
|
||||||
|
.rpc
|
||||||
|
.send_request(ApiMethod::Latest.selector(), RpcType::Async, &args)
|
||||||
|
.await?;
|
||||||
Ok(req_no)
|
Ok(req_no)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
|
|
||||||
use sodiumoxide::crypto::sign::ed25519;
|
|
||||||
use sodiumoxide::crypto::auth;
|
use sodiumoxide::crypto::auth;
|
||||||
|
use sodiumoxide::crypto::sign::ed25519;
|
||||||
|
|
||||||
use crate::crypto::ToSodiumObject;
|
use crate::crypto::ToSodiumObject;
|
||||||
|
|
||||||
use super::error::{Error,Result};
|
use super::error::{Error, Result};
|
||||||
|
|
||||||
const CURVE_ED25519 : &str = "ed25519";
|
const CURVE_ED25519: &str = "ed25519";
|
||||||
pub const SSB_NET_ID : &str = "d4a1cb88a66f02f8db635ce26441cc5dac1b08420ceaac230839b755845a9ffb";
|
pub const SSB_NET_ID: &str = "d4a1cb88a66f02f8db635ce26441cc5dac1b08420ceaac230839b755845a9ffb";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct IdentitySecret {
|
pub struct IdentitySecret {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub pk: ed25519::PublicKey,
|
pub pk: ed25519::PublicKey,
|
||||||
|
@ -36,45 +36,42 @@ fn to_ioerr<T: ToString>(err: T) -> io::Error {
|
||||||
|
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
impl IdentitySecret {
|
impl IdentitySecret {
|
||||||
|
|
||||||
pub fn new() -> IdentitySecret {
|
pub fn new() -> IdentitySecret {
|
||||||
let (pk, sk) = ed25519::gen_keypair();
|
let (pk, sk) = ed25519::gen_keypair();
|
||||||
IdentitySecret {
|
IdentitySecret {
|
||||||
pk, sk,
|
pk,
|
||||||
id : format!("@{}.{}",base64::encode(&pk),CURVE_ED25519),
|
sk,
|
||||||
|
id: format!("@{}.{}", base64::encode(&pk), CURVE_ED25519),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_local_config() -> Result<IdentitySecret> {
|
pub fn from_local_config() -> Result<IdentitySecret> {
|
||||||
let home_dir = dirs::home_dir().ok_or(Error::HomeNotFound)?;
|
let home_dir = dirs::home_dir().ok_or(Error::HomeNotFound)?;
|
||||||
let local_key_file = format!("{}/.ssb/secret",home_dir.to_string_lossy());
|
let local_key_file = format!("{}/.ssb/secret", home_dir.to_string_lossy());
|
||||||
let content = std::fs::read_to_string(local_key_file)?;
|
let content = std::fs::read_to_string(local_key_file)?;
|
||||||
Ok(IdentitySecret::from_config(content)?)
|
Ok(IdentitySecret::from_config(content)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_config<T : AsRef<str>>(config: T) -> Result<IdentitySecret> {
|
pub fn from_config<T: AsRef<str>>(config: T) -> Result<IdentitySecret> {
|
||||||
|
|
||||||
// strip all comments
|
// strip all comments
|
||||||
let json = config.as_ref()
|
let json = config
|
||||||
|
.as_ref()
|
||||||
.lines()
|
.lines()
|
||||||
.filter(|line| !line.starts_with('#'))
|
.filter(|line| !line.starts_with('#'))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
// parse json
|
// parse json
|
||||||
let secret : JsonSSBSecret = serde_json::from_str(json.as_ref())
|
let secret: JsonSSBSecret = serde_json::from_str(json.as_ref()).map_err(to_ioerr)?;
|
||||||
.map_err(to_ioerr)?;
|
|
||||||
|
|
||||||
if secret.curve != CURVE_ED25519 {
|
if secret.curve != CURVE_ED25519 {
|
||||||
return Err(Error::InvalidConfig);
|
return Err(Error::InvalidConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(IdentitySecret {
|
Ok(IdentitySecret {
|
||||||
id : secret.id,
|
id: secret.id,
|
||||||
pk : secret.public.to_ed25519_pk()?,
|
pk: secret.public.to_ed25519_pk()?,
|
||||||
sk : secret.private.to_ed25519_sk()?,
|
sk: secret.private.to_ed25519_sk()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,6 @@ impl std::fmt::Display for Error {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{:?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::error::Error for Error { }
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
mod api;
|
mod api;
|
||||||
mod config;
|
mod config;
|
||||||
mod messagetypes;
|
|
||||||
mod error;
|
mod error;
|
||||||
|
pub mod msgs;
|
||||||
pub mod pubs;
|
pub mod pubs;
|
||||||
|
|
||||||
pub use api::{
|
pub use api::{
|
||||||
parse_feed, parse_latest, parse_message, parse_whoami, ApiClient, CreateHistoryStreamArgs,
|
ApiHelper, ApiMethod, CreateHistoryStreamArgs, CreateStreamArgs, LatestUserMessage, WhoAmI,
|
||||||
CreateStreamArgs,
|
|
||||||
};
|
};
|
||||||
pub use config::{ssb_net_id, IdentitySecret};
|
pub use config::{ssb_net_id, IdentitySecret};
|
||||||
pub use error::{Error,Result};
|
pub use error::{Error, Result};
|
||||||
|
|
|
@ -5,21 +5,33 @@ use std::collections::HashMap;
|
||||||
pub type SsbHash = String;
|
pub type SsbHash = String;
|
||||||
pub type SsbId = String;
|
pub type SsbId = String;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Mention {
|
pub struct Mention {
|
||||||
pub link: SsbId,
|
pub link: SsbId,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct MessageContent {
|
pub struct Post {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub xtype: String,
|
pub xtype: String,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub mentions: Option<Vec<Mention>>,
|
pub mentions: Option<Vec<Mention>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Post {
|
||||||
|
pub fn new(text: String, mentions: Option<Vec<Mention>>) -> Self {
|
||||||
|
Post {
|
||||||
|
xtype: String::from("post"),
|
||||||
|
text,
|
||||||
|
mentions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn to_msg(&self) -> serde_json::Result<serde_json::Value> {
|
||||||
|
serde_json::to_value(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct PubAddress {
|
pub struct PubAddress {
|
||||||
pub host: Option<String>,
|
pub host: Option<String>,
|
||||||
|
@ -84,16 +96,7 @@ pub enum FeedTypedContent {
|
||||||
#[serde(rename = "pub")]
|
#[serde(rename = "pub")]
|
||||||
Pub { address: Option<PubAddress> },
|
Pub { address: Option<PubAddress> },
|
||||||
#[serde(rename = "post")]
|
#[serde(rename = "post")]
|
||||||
Post {
|
Post,
|
||||||
text: Option<String>,
|
|
||||||
post: Option<String>, // the same than text
|
|
||||||
channel: Option<String>,
|
|
||||||
mentions: Option<Mentions>,
|
|
||||||
root: Option<SsbHash>,
|
|
||||||
branch: Option<Branch>,
|
|
||||||
reply: Option<HashMap<SsbHash, SsbId>>,
|
|
||||||
recps: Option<String>,
|
|
||||||
},
|
|
||||||
#[serde(rename = "contact")]
|
#[serde(rename = "contact")]
|
||||||
Contact {
|
Contact {
|
||||||
contact: Option<SsbId>,
|
contact: Option<SsbId>,
|
||||||
|
@ -118,4 +121,3 @@ pub enum FeedTypedContent {
|
||||||
#[serde(rename = "vote")]
|
#[serde(rename = "vote")]
|
||||||
Vote { vote: Vote },
|
Vote { vote: Vote },
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
use sodiumoxide::crypto::sign::ed25519;
|
|
||||||
use crate::crypto::ToSodiumObject;
|
use crate::crypto::ToSodiumObject;
|
||||||
|
use sodiumoxide::crypto::sign::ed25519;
|
||||||
|
|
||||||
use super::error::{Error,Result};
|
use super::error::{Error, Result};
|
||||||
|
|
||||||
pub struct Invite {
|
pub struct Invite {
|
||||||
pub domain : String,
|
pub domain: String,
|
||||||
pub port : u16,
|
pub port: u16,
|
||||||
pub pub_pk: ed25519::PublicKey,
|
pub pub_pk: ed25519::PublicKey,
|
||||||
pub invite_sk: ed25519::SecretKey,
|
pub invite_sk: ed25519::SecretKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Invite {
|
impl Invite {
|
||||||
pub fn from_code(code : &str) -> Result<Self> {
|
pub fn from_code(code: &str) -> Result<Self> {
|
||||||
let domain_port_keys : Vec<_> = code.split(':').collect();
|
let domain_port_keys: Vec<_> = code.split(':').collect();
|
||||||
if domain_port_keys.len() != 3 {
|
if domain_port_keys.len() != 3 {
|
||||||
return Err(Error::InvalidInviteCode);
|
return Err(Error::InvalidInviteCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
let domain = domain_port_keys[0].to_string();
|
let domain = domain_port_keys[0].to_string();
|
||||||
let port = domain_port_keys[1].parse::<u16>()?;
|
let port = domain_port_keys[1].parse::<u16>()?;
|
||||||
let pk_sk :Vec<_> = domain_port_keys[2].split('~').collect();
|
let pk_sk: Vec<_> = domain_port_keys[2].split('~').collect();
|
||||||
|
|
||||||
if pk_sk.len() != 2 {
|
if pk_sk.len() != 2 {
|
||||||
return Err(Error::InvalidInviteCode);
|
return Err(Error::InvalidInviteCode);
|
||||||
|
@ -27,9 +27,13 @@ impl Invite {
|
||||||
let pub_pk = pk_sk[0][1..].to_ed25519_pk()?;
|
let pub_pk = pk_sk[0][1..].to_ed25519_pk()?;
|
||||||
let invite_sk = pk_sk[1][..].to_ed25519_sk_no_suffix()?;
|
let invite_sk = pk_sk[1][..].to_ed25519_sk_no_suffix()?;
|
||||||
|
|
||||||
Ok(Invite { domain, port, pub_pk, invite_sk })
|
Ok(Invite {
|
||||||
|
domain,
|
||||||
|
port,
|
||||||
|
pub_pk,
|
||||||
|
invite_sk,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,290 +0,0 @@
|
||||||
|
|
||||||
use super::error::{Error,Result};
|
|
||||||
|
|
||||||
use async_std::io;
|
|
||||||
use async_std::prelude::*;
|
|
||||||
|
|
||||||
use kuska_handshake::async_std::{
|
|
||||||
BoxStreamRead,
|
|
||||||
BoxStreamWrite
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type RequestNo = i32;
|
|
||||||
|
|
||||||
const HEADER_SIZE : usize = 9;
|
|
||||||
|
|
||||||
const RPC_HEADER_STREAM_FLAG : u8 = 1 << 3;
|
|
||||||
const RPC_HEADER_END_OR_ERROR_FLAG : u8 = 1 << 2;
|
|
||||||
const RPC_HEADER_BODY_TYPE_MASK : u8 = 0b11;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug,PartialEq)]
|
|
||||||
pub enum BodyType {
|
|
||||||
Binary,
|
|
||||||
UTF8,
|
|
||||||
JSON,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct Body {
|
|
||||||
pub name : Vec<String>,
|
|
||||||
#[serde(rename="type")]
|
|
||||||
pub rpc_type : RpcType,
|
|
||||||
pub args : serde_json::Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct BodyRef<'a,T:serde::Serialize> {
|
|
||||||
pub name : &'a [&'a str],
|
|
||||||
#[serde(rename="type")]
|
|
||||||
pub rpc_type : RpcType,
|
|
||||||
pub args : &'a T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a,T:serde::Serialize> BodyRef<'a,T> {
|
|
||||||
pub fn new(name:&'a [&'a str], rpc_type : RpcType, args:&'a T) -> Self {
|
|
||||||
BodyRef { name, rpc_type, args }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Copy,Serialize,Deserialize,Debug,PartialEq)]
|
|
||||||
pub enum RpcType {
|
|
||||||
#[serde(rename="async")]
|
|
||||||
Async,
|
|
||||||
#[serde(rename="source")]
|
|
||||||
Source,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,PartialEq)]
|
|
||||||
pub struct Header {
|
|
||||||
pub req_no : RequestNo,
|
|
||||||
pub is_stream : bool,
|
|
||||||
pub is_end_or_error : bool,
|
|
||||||
pub body_type : BodyType,
|
|
||||||
pub body_len : u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize,Deserialize)]
|
|
||||||
struct ErrorMessage<'a> {
|
|
||||||
name : &'a str,
|
|
||||||
message : &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Header {
|
|
||||||
pub fn from_slice(bytes: &[u8]) -> Result<Header> {
|
|
||||||
if bytes.len() < HEADER_SIZE {
|
|
||||||
return Err(Error::HeaderSizeTooSmall);
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_stream = (bytes[0] & RPC_HEADER_STREAM_FLAG) == RPC_HEADER_STREAM_FLAG;
|
|
||||||
let is_end_or_error = (bytes[0] & RPC_HEADER_END_OR_ERROR_FLAG) == RPC_HEADER_END_OR_ERROR_FLAG;
|
|
||||||
let body_type = match bytes[0] & RPC_HEADER_BODY_TYPE_MASK {
|
|
||||||
0 => BodyType::Binary,
|
|
||||||
1 => BodyType::UTF8,
|
|
||||||
2 => BodyType::JSON,
|
|
||||||
_ => return Err(Error::InvalidBodyType),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut body_len_buff = [0u8;4];
|
|
||||||
body_len_buff.copy_from_slice(&bytes[1..5]);
|
|
||||||
let body_len = u32::from_be_bytes(body_len_buff);
|
|
||||||
|
|
||||||
let mut reqno_buff = [0u8;4];
|
|
||||||
reqno_buff.copy_from_slice(&bytes[5..9]);
|
|
||||||
let req_no = i32::from_be_bytes(reqno_buff);
|
|
||||||
|
|
||||||
Ok(Header{
|
|
||||||
req_no, is_stream, is_end_or_error, body_type, body_len
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_array(&self) -> [u8;9] {
|
|
||||||
let mut flags : u8 = 0;
|
|
||||||
if self.is_end_or_error {
|
|
||||||
flags |= RPC_HEADER_END_OR_ERROR_FLAG;
|
|
||||||
}
|
|
||||||
if self.is_stream {
|
|
||||||
flags |= RPC_HEADER_STREAM_FLAG;
|
|
||||||
}
|
|
||||||
flags |= match self.body_type {
|
|
||||||
BodyType::Binary => 0,
|
|
||||||
BodyType::UTF8 => 1,
|
|
||||||
BodyType::JSON => 2,
|
|
||||||
};
|
|
||||||
let len = self.body_len.to_be_bytes();
|
|
||||||
let req_no = self.req_no.to_be_bytes();
|
|
||||||
|
|
||||||
let mut encoded = [0u8;9];
|
|
||||||
encoded[0] = flags;
|
|
||||||
encoded[1..5].copy_from_slice(&len[..]);
|
|
||||||
encoded[5..9].copy_from_slice(&req_no[..]);
|
|
||||||
|
|
||||||
encoded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RpcStream<R : io::Read + Unpin, W : io::Write + Unpin> {
|
|
||||||
box_reader : BoxStreamRead<R>,
|
|
||||||
box_writer : BoxStreamWrite<W>,
|
|
||||||
req_no : RequestNo,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum RecvMsg {
|
|
||||||
Request(Body),
|
|
||||||
ErrorResponse(String),
|
|
||||||
CancelStreamRespose(),
|
|
||||||
BodyResponse(Vec<u8>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R:io::Read+Unpin , W:io::Write+Unpin> RpcStream<R,W> {
|
|
||||||
|
|
||||||
pub fn new(box_reader :BoxStreamRead<R>, box_writer :BoxStreamWrite<W>) -> RpcStream<R,W> {
|
|
||||||
RpcStream { box_reader, box_writer, req_no : 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn recv(&mut self) -> Result<(RequestNo,RecvMsg)> {
|
|
||||||
let mut rpc_header_raw = [0u8;9];
|
|
||||||
self.box_reader.read_exact(&mut rpc_header_raw[..]).await?;
|
|
||||||
let rpc_header = Header::from_slice(&rpc_header_raw[..])?;
|
|
||||||
|
|
||||||
let mut body_raw : Vec<u8> = vec![0;rpc_header.body_len as usize];
|
|
||||||
self.box_reader.read_exact(&mut body_raw[..]).await?;
|
|
||||||
|
|
||||||
if rpc_header.req_no > 0 {
|
|
||||||
let rpc_body = serde_json::from_slice(&body_raw)?;
|
|
||||||
Ok((rpc_header.req_no,RecvMsg::Request(rpc_body)))
|
|
||||||
} else {
|
|
||||||
if rpc_header.is_end_or_error {
|
|
||||||
if rpc_header.is_stream {
|
|
||||||
Ok((-rpc_header.req_no,RecvMsg::CancelStreamRespose()))
|
|
||||||
} else {
|
|
||||||
let err : ErrorMessage = serde_json::from_slice(&body_raw)?;
|
|
||||||
Ok((-rpc_header.req_no,RecvMsg::ErrorResponse(err.message.to_string())))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok((-rpc_header.req_no,RecvMsg::BodyResponse(body_raw)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_request<T:serde::Serialize>(&mut self, name: &[&str], rpc_type: RpcType, args : &T) -> Result<RequestNo>{
|
|
||||||
|
|
||||||
self.req_no+=1;
|
|
||||||
|
|
||||||
let body_str = serde_json::to_string(&BodyRef {
|
|
||||||
name,
|
|
||||||
rpc_type,
|
|
||||||
args: &[&args],
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let rpc_header = Header {
|
|
||||||
req_no : self.req_no,
|
|
||||||
is_stream : rpc_type == RpcType::Source,
|
|
||||||
is_end_or_error : false,
|
|
||||||
body_type : BodyType::JSON,
|
|
||||||
body_len : body_str.as_bytes().len() as u32,
|
|
||||||
}.to_array();
|
|
||||||
|
|
||||||
self.box_writer.write_all(&rpc_header[..]).await?;
|
|
||||||
self.box_writer.write_all(body_str.as_bytes()).await?;
|
|
||||||
self.box_writer.flush().await?;
|
|
||||||
|
|
||||||
Ok(self.req_no)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_response(&mut self, req_no : RequestNo, rpc_type: RpcType, body_type : BodyType, body: &[u8] ) -> Result<()>{
|
|
||||||
self.req_no+=1;
|
|
||||||
|
|
||||||
let rpc_header = Header {
|
|
||||||
req_no,
|
|
||||||
is_stream : rpc_type == RpcType::Source,
|
|
||||||
is_end_or_error : false,
|
|
||||||
body_type : body_type,
|
|
||||||
body_len : body.len() as u32,
|
|
||||||
}.to_array();
|
|
||||||
|
|
||||||
self.box_writer.write_all(&rpc_header[..]).await?;
|
|
||||||
self.box_writer.write_all(body).await?;
|
|
||||||
self.box_writer.flush().await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_error(&mut self, req_no: RequestNo, message: &str) -> Result<()> {
|
|
||||||
let body_bytes = serde_json::to_string(&ErrorMessage {
|
|
||||||
name : "Error",
|
|
||||||
message
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let rpc_header = Header {
|
|
||||||
req_no,
|
|
||||||
is_stream : false,
|
|
||||||
is_end_or_error : true,
|
|
||||||
body_type : BodyType::UTF8,
|
|
||||||
body_len : body_bytes.as_bytes().len() as u32,
|
|
||||||
}.to_array();
|
|
||||||
|
|
||||||
self.box_writer.write_all(&rpc_header[..]).await?;
|
|
||||||
self.box_writer.write_all(body_bytes.as_bytes()).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_cancel(&mut self, req_no: RequestNo) -> Result<()> {
|
|
||||||
let body_bytes = b"true";
|
|
||||||
|
|
||||||
let rpc_header = Header {
|
|
||||||
req_no,
|
|
||||||
is_stream : true,
|
|
||||||
is_end_or_error : true,
|
|
||||||
body_type : BodyType::JSON,
|
|
||||||
body_len : body_bytes.len() as u32,
|
|
||||||
}.to_array();
|
|
||||||
|
|
||||||
self.box_writer.write_all(&rpc_header[..]).await?;
|
|
||||||
self.box_writer.write_all(&body_bytes[..]).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn close(&mut self) -> Result<()> {
|
|
||||||
self.box_writer.goodbye().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::{Header,BodyType};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_header_encoding_1() {
|
|
||||||
let h = Header::from_slice(&(Header{
|
|
||||||
req_no : 5,
|
|
||||||
is_stream : true,
|
|
||||||
is_end_or_error : false,
|
|
||||||
body_type : BodyType::JSON,
|
|
||||||
body_len : 123,
|
|
||||||
}.to_array())[..]).unwrap();
|
|
||||||
assert_eq!(h.req_no,5);
|
|
||||||
assert_eq!(h.is_stream, true);
|
|
||||||
assert_eq!(h.is_end_or_error, false);
|
|
||||||
assert_eq!(h.body_type, BodyType::JSON);
|
|
||||||
assert_eq!(h.body_len, 123);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_header_encoding_2() {
|
|
||||||
let h = Header::from_slice(&(Header{
|
|
||||||
req_no : -5,
|
|
||||||
is_stream : false,
|
|
||||||
is_end_or_error : true,
|
|
||||||
body_type : BodyType::Binary,
|
|
||||||
body_len : 2123,
|
|
||||||
}.to_array())[..]).unwrap();
|
|
||||||
assert_eq!(h.req_no,-5);
|
|
||||||
assert_eq!(h.is_stream, false);
|
|
||||||
assert_eq!(h.is_end_or_error, true);
|
|
||||||
assert_eq!(h.body_type, BodyType::Binary);
|
|
||||||
assert_eq!(h.body_len, 2123);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,7 +23,6 @@ impl std::fmt::Display for Error {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{:?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::error::Error for Error { }
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod client;
|
|
||||||
mod error;
|
mod error;
|
||||||
|
mod stream;
|
||||||
|
|
||||||
pub use client::{RpcStream,RecvMsg, RequestNo, RpcType, Body, BodyRef};
|
pub use error::{Error, Result};
|
||||||
pub use error::{Error,Result};
|
pub use stream::{Body, BodyType, RecvMsg, RequestNo, RpcStream, RpcType};
|
||||||
|
|
|
@ -0,0 +1,310 @@
|
||||||
|
use super::error::{Error, Result};
|
||||||
|
|
||||||
|
use async_std::io;
|
||||||
|
use async_std::prelude::*;
|
||||||
|
|
||||||
|
use kuska_handshake::async_std::{BoxStreamRead, BoxStreamWrite};
|
||||||
|
|
||||||
|
pub type RequestNo = i32;
|
||||||
|
|
||||||
|
const HEADER_SIZE: usize = 9;
|
||||||
|
|
||||||
|
const RPC_HEADER_STREAM_FLAG: u8 = 1 << 3;
|
||||||
|
const RPC_HEADER_END_OR_ERROR_FLAG: u8 = 1 << 2;
|
||||||
|
const RPC_HEADER_BODY_TYPE_MASK: u8 = 0b11;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub enum BodyType {
|
||||||
|
Binary,
|
||||||
|
UTF8,
|
||||||
|
JSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Body {
|
||||||
|
pub name: Vec<String>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub rpc_type: RpcType,
|
||||||
|
pub args: serde_json::Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct BodyRef<'a, T: serde::Serialize> {
|
||||||
|
pub name: &'a [&'a str],
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub rpc_type: RpcType,
|
||||||
|
pub args: &'a T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
pub enum RpcType {
|
||||||
|
#[serde(rename = "async")]
|
||||||
|
Async,
|
||||||
|
#[serde(rename = "source")]
|
||||||
|
Source,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Header {
|
||||||
|
pub req_no: RequestNo,
|
||||||
|
pub is_stream: bool,
|
||||||
|
pub is_end_or_error: bool,
|
||||||
|
pub body_type: BodyType,
|
||||||
|
pub body_len: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct ErrorMessage<'a> {
|
||||||
|
name: &'a str,
|
||||||
|
message: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
pub fn from_slice(bytes: &[u8]) -> Result<Header> {
|
||||||
|
if bytes.len() < HEADER_SIZE {
|
||||||
|
return Err(Error::HeaderSizeTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_stream = (bytes[0] & RPC_HEADER_STREAM_FLAG) == RPC_HEADER_STREAM_FLAG;
|
||||||
|
let is_end_or_error =
|
||||||
|
(bytes[0] & RPC_HEADER_END_OR_ERROR_FLAG) == RPC_HEADER_END_OR_ERROR_FLAG;
|
||||||
|
let body_type = match bytes[0] & RPC_HEADER_BODY_TYPE_MASK {
|
||||||
|
0 => BodyType::Binary,
|
||||||
|
1 => BodyType::UTF8,
|
||||||
|
2 => BodyType::JSON,
|
||||||
|
_ => return Err(Error::InvalidBodyType),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut body_len_buff = [0u8; 4];
|
||||||
|
body_len_buff.copy_from_slice(&bytes[1..5]);
|
||||||
|
let body_len = u32::from_be_bytes(body_len_buff);
|
||||||
|
|
||||||
|
let mut reqno_buff = [0u8; 4];
|
||||||
|
reqno_buff.copy_from_slice(&bytes[5..9]);
|
||||||
|
let req_no = i32::from_be_bytes(reqno_buff);
|
||||||
|
|
||||||
|
Ok(Header {
|
||||||
|
req_no,
|
||||||
|
is_stream,
|
||||||
|
is_end_or_error,
|
||||||
|
body_type,
|
||||||
|
body_len,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_array(&self) -> [u8; 9] {
|
||||||
|
let mut flags: u8 = 0;
|
||||||
|
if self.is_end_or_error {
|
||||||
|
flags |= RPC_HEADER_END_OR_ERROR_FLAG;
|
||||||
|
}
|
||||||
|
if self.is_stream {
|
||||||
|
flags |= RPC_HEADER_STREAM_FLAG;
|
||||||
|
}
|
||||||
|
flags |= match self.body_type {
|
||||||
|
BodyType::Binary => 0,
|
||||||
|
BodyType::UTF8 => 1,
|
||||||
|
BodyType::JSON => 2,
|
||||||
|
};
|
||||||
|
let len = self.body_len.to_be_bytes();
|
||||||
|
let req_no = self.req_no.to_be_bytes();
|
||||||
|
|
||||||
|
let mut encoded = [0u8; 9];
|
||||||
|
encoded[0] = flags;
|
||||||
|
encoded[1..5].copy_from_slice(&len[..]);
|
||||||
|
encoded[5..9].copy_from_slice(&req_no[..]);
|
||||||
|
|
||||||
|
encoded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RpcStream<R: io::Read + Unpin, W: io::Write + Unpin> {
|
||||||
|
box_reader: BoxStreamRead<R>,
|
||||||
|
box_writer: BoxStreamWrite<W>,
|
||||||
|
req_no: RequestNo,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RecvMsg {
|
||||||
|
Request(Body),
|
||||||
|
ErrorResponse(String),
|
||||||
|
CancelStreamRespose(),
|
||||||
|
BodyResponse(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: io::Read + Unpin, W: io::Write + Unpin> RpcStream<R, W> {
|
||||||
|
pub fn new(box_reader: BoxStreamRead<R>, box_writer: BoxStreamWrite<W>) -> RpcStream<R, W> {
|
||||||
|
RpcStream {
|
||||||
|
box_reader,
|
||||||
|
box_writer,
|
||||||
|
req_no: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn recv(&mut self) -> Result<(RequestNo, RecvMsg)> {
|
||||||
|
let mut rpc_header_raw = [0u8; 9];
|
||||||
|
self.box_reader.read_exact(&mut rpc_header_raw[..]).await?;
|
||||||
|
let rpc_header = Header::from_slice(&rpc_header_raw[..])?;
|
||||||
|
|
||||||
|
let mut body_raw: Vec<u8> = vec![0; rpc_header.body_len as usize];
|
||||||
|
self.box_reader.read_exact(&mut body_raw[..]).await?;
|
||||||
|
|
||||||
|
if rpc_header.req_no > 0 {
|
||||||
|
let rpc_body = serde_json::from_slice(&body_raw)?;
|
||||||
|
Ok((rpc_header.req_no, RecvMsg::Request(rpc_body)))
|
||||||
|
} else if rpc_header.is_end_or_error {
|
||||||
|
if rpc_header.is_stream {
|
||||||
|
Ok((-rpc_header.req_no, RecvMsg::CancelStreamRespose()))
|
||||||
|
} else {
|
||||||
|
let err: ErrorMessage = serde_json::from_slice(&body_raw)?;
|
||||||
|
Ok((
|
||||||
|
-rpc_header.req_no,
|
||||||
|
RecvMsg::ErrorResponse(err.message.to_string()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok((-rpc_header.req_no, RecvMsg::BodyResponse(body_raw)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_request<T: serde::Serialize>(
|
||||||
|
&mut self,
|
||||||
|
name: &[&str],
|
||||||
|
rpc_type: RpcType,
|
||||||
|
args: &T,
|
||||||
|
) -> Result<RequestNo> {
|
||||||
|
self.req_no += 1;
|
||||||
|
|
||||||
|
let body_str = serde_json::to_string(&BodyRef {
|
||||||
|
name,
|
||||||
|
rpc_type,
|
||||||
|
args: &[&args],
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let rpc_header = Header {
|
||||||
|
req_no: self.req_no,
|
||||||
|
is_stream: rpc_type == RpcType::Source,
|
||||||
|
is_end_or_error: false,
|
||||||
|
body_type: BodyType::JSON,
|
||||||
|
body_len: body_str.as_bytes().len() as u32,
|
||||||
|
}
|
||||||
|
.to_array();
|
||||||
|
|
||||||
|
self.box_writer.write_all(&rpc_header[..]).await?;
|
||||||
|
self.box_writer.write_all(body_str.as_bytes()).await?;
|
||||||
|
self.box_writer.flush().await?;
|
||||||
|
|
||||||
|
Ok(self.req_no)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_response(
|
||||||
|
&mut self,
|
||||||
|
req_no: RequestNo,
|
||||||
|
rpc_type: RpcType,
|
||||||
|
body_type: BodyType,
|
||||||
|
body: &[u8],
|
||||||
|
) -> Result<()> {
|
||||||
|
let rpc_header = Header {
|
||||||
|
req_no: -req_no,
|
||||||
|
is_stream: rpc_type == RpcType::Source,
|
||||||
|
is_end_or_error: false,
|
||||||
|
body_type,
|
||||||
|
body_len: body.len() as u32,
|
||||||
|
}
|
||||||
|
.to_array();
|
||||||
|
|
||||||
|
self.box_writer.write_all(&rpc_header[..]).await?;
|
||||||
|
self.box_writer.write_all(body).await?;
|
||||||
|
self.box_writer.flush().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_error(&mut self, req_no: RequestNo, message: &str) -> Result<()> {
|
||||||
|
let body_bytes = serde_json::to_string(&ErrorMessage {
|
||||||
|
name: "Error",
|
||||||
|
message,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let rpc_header = Header {
|
||||||
|
req_no: -req_no,
|
||||||
|
is_stream: false,
|
||||||
|
is_end_or_error: true,
|
||||||
|
body_type: BodyType::UTF8,
|
||||||
|
body_len: body_bytes.as_bytes().len() as u32,
|
||||||
|
}
|
||||||
|
.to_array();
|
||||||
|
|
||||||
|
self.box_writer.write_all(&rpc_header[..]).await?;
|
||||||
|
self.box_writer.write_all(body_bytes.as_bytes()).await?;
|
||||||
|
self.box_writer.flush().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_stream_eof(&mut self, req_no: RequestNo) -> Result<()> {
|
||||||
|
let body_bytes = b"true";
|
||||||
|
|
||||||
|
let rpc_header = Header {
|
||||||
|
req_no: -req_no,
|
||||||
|
is_stream: true,
|
||||||
|
is_end_or_error: true,
|
||||||
|
body_type: BodyType::JSON,
|
||||||
|
body_len: body_bytes.len() as u32,
|
||||||
|
}
|
||||||
|
.to_array();
|
||||||
|
|
||||||
|
self.box_writer.write_all(&rpc_header[..]).await?;
|
||||||
|
self.box_writer.write_all(&body_bytes[..]).await?;
|
||||||
|
self.box_writer.flush().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn close(&mut self) -> Result<()> {
|
||||||
|
self.box_writer.goodbye().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::{BodyType, Header};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_header_encoding_1() {
|
||||||
|
let h = Header::from_slice(
|
||||||
|
&(Header {
|
||||||
|
req_no: 5,
|
||||||
|
is_stream: true,
|
||||||
|
is_end_or_error: false,
|
||||||
|
body_type: BodyType::JSON,
|
||||||
|
body_len: 123,
|
||||||
|
}
|
||||||
|
.to_array())[..],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(h.req_no, 5);
|
||||||
|
assert_eq!(h.is_stream, true);
|
||||||
|
assert_eq!(h.is_end_or_error, false);
|
||||||
|
assert_eq!(h.body_type, BodyType::JSON);
|
||||||
|
assert_eq!(h.body_len, 123);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_header_encoding_2() {
|
||||||
|
let h = Header::from_slice(
|
||||||
|
&(Header {
|
||||||
|
req_no: -5,
|
||||||
|
is_stream: false,
|
||||||
|
is_end_or_error: true,
|
||||||
|
body_type: BodyType::Binary,
|
||||||
|
body_len: 2123,
|
||||||
|
}
|
||||||
|
.to_array())[..],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(h.req_no, -5);
|
||||||
|
assert_eq!(h.is_stream, false);
|
||||||
|
assert_eq!(h.is_end_or_error, true);
|
||||||
|
assert_eq!(h.body_type, BodyType::Binary);
|
||||||
|
assert_eq!(h.body_len, 2123);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue