| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use async_std::{
- stream::StreamExt,
- sync::{Arc, Mutex},
- };
- use libc::mkfifo;
- use std::{
- collections::HashMap,
- env,
- ffi::CString,
- fs::{create_dir_all, remove_dir_all},
- io::{stdin, Write},
- path::Path,
- };
- use crypto_box::{
- aead::{Aead, AeadCore},
- rand_core::OsRng,
- SalsaBox, SecretKey,
- };
- use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable};
- use futures::{select, FutureExt};
- use log::{debug, error, info};
- use structopt_toml::StructOptToml;
- use darkfi::{
- async_daemonize,
- event_graph::{
- events_queue::EventsQueue,
- model::{Event, EventId, Model, ModelPtr},
- protocol_event::{ProtocolEvent, Seen, SeenPtr},
- view::{View, ViewPtr},
- EventMsg,
- },
- net::{self, P2pPtr},
- rpc::server::listen_and_serve,
- util::{path::expand_path, time::Timestamp},
- Error, Result,
- };
- mod error;
- mod jsonrpc;
- mod month_tasks;
- mod settings;
- mod task_info;
- mod util;
- use crate::{
- error::TaudResult,
- jsonrpc::JsonRpcInterface,
- settings::{Args, CONFIG_FILE, CONFIG_FILE_CONTENTS},
- task_info::{TaskEvent, TaskInfo},
- util::pipe_write,
- };
- fn get_workspaces(settings: &Args) -> Result<HashMap<String, SalsaBox>> {
- let mut workspaces = HashMap::new();
- for workspace in settings.workspaces.iter() {
- let workspace: Vec<&str> = workspace.split(':').collect();
- let (workspace, secret) = (workspace[0], workspace[1]);
- let bytes: [u8; 32] = bs58::decode(secret)
- .into_vec()?
- .try_into()
- .map_err(|_| Error::ParseFailed("Parse secret key failed"))?;
- let secret = crypto_box::SecretKey::from(bytes);
- let public = secret.public_key();
- let salsa_box = crypto_box::SalsaBox::new(&public, &secret);
- workspaces.insert(workspace.to_string(), salsa_box);
- }
- Ok(workspaces)
- }
- #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
- pub struct EncryptedTask {
- nonce: Vec<u8>,
- payload: Vec<u8>,
- }
- impl EventMsg for EncryptedTask {
- fn new() -> Self {
- Self {
- nonce: [
- 19, 40, 199, 87, 248, 23, 187, 11, 119, 237, 214, 65, 5, 206, 187, 33, 222, 107,
- 140, 84, 114, 61, 205, 40,
- ]
- .to_vec(),
- payload: [
- 30, 66, 74, 74, 65, 78, 80, 120, 85, 66, 106, 119, 119, 81, 66, 55, 112, 80, 88,
- 85, 82, 97, 79, 108, 115, 83, 113, 78, 71, 116, 113, 4, 114, 111, 111, 116, 1, 0,
- 0, 0, 5, 116, 105, 116, 108, 101, 0, 4, 100, 101, 115, 99, 6, 100, 97, 114, 107,
- 102, 105, 0, 0, 0, 0, 42, 47, 14, 100, 0, 0, 0, 0, 4, 111, 112, 101, 110, 0, 0,
- ]
- .to_vec(),
- }
- }
- }
- fn encrypt_task(
- task: &TaskInfo,
- salsa_box: &SalsaBox,
- rng: &mut OsRng,
- ) -> TaudResult<EncryptedTask> {
- debug!("start encrypting task");
- let nonce = SalsaBox::generate_nonce(rng);
- let payload = &serialize(task)[..];
- let payload = salsa_box.encrypt(&nonce, payload)?;
- let nonce = nonce.to_vec();
- Ok(EncryptedTask { nonce, payload })
- }
- fn decrypt_task(encrypt_task: &EncryptedTask, salsa_box: &SalsaBox) -> TaudResult<TaskInfo> {
- debug!("start decrypting task");
- let nonce = encrypt_task.nonce.as_slice();
- let decrypted_task = salsa_box.decrypt(nonce.into(), &encrypt_task.payload[..])?;
- let task = deserialize(&decrypted_task)?;
- Ok(task)
- }
- #[allow(clippy::too_many_arguments)]
- async fn start_sync_loop(
- broadcast_rcv: smol::channel::Receiver<TaskInfo>,
- view: ViewPtr<EncryptedTask>,
- model: ModelPtr<EncryptedTask>,
- seen: SeenPtr<EventId>,
- workspaces: HashMap<String, SalsaBox>,
- datastore_path: std::path::PathBuf,
- missed_events: Arc<Mutex<Vec<Event<EncryptedTask>>>>,
- piped: bool,
- p2p: P2pPtr,
- ) -> TaudResult<()> {
- loop {
- let mut v = view.lock().await;
- select! {
- task_event = broadcast_rcv.recv().fuse() => {
- let tk = task_event.map_err(Error::from)?;
- if workspaces.contains_key(&tk.workspace) {
- let salsa_box = workspaces.get(&tk.workspace).unwrap();
- let encrypted_task = encrypt_task(&tk, salsa_box, &mut OsRng)?;
- info!(target: "tau", "Send the task: ref: {}", tk.ref_id);
- let event = Event {
- previous_event_hash: model.lock().await.get_head_hash(),
- action: encrypted_task,
- timestamp: Timestamp::current_time(),
- };
- p2p.broadcast(&event).await;
- }
- }
- task_event = v.process().fuse() => {
- let event = task_event.map_err(Error::from)?;
- if !seen.push(&event.hash()).await {
- continue
- }
- missed_events.lock().await.push(event.clone());
- on_receive_task(&event.action, &datastore_path, &workspaces, piped)
- .await?;
- }
- }
- }
- }
- async fn on_receive_task(
- task: &EncryptedTask,
- datastore_path: &Path,
- workspaces: &HashMap<String, SalsaBox>,
- piped: bool,
- ) -> TaudResult<()> {
- for (workspace, salsa_box) in workspaces.iter() {
- let task = decrypt_task(task, salsa_box);
- if let Err(e) = task {
- debug!("unable to decrypt the task: {}", e);
- continue
- }
- let mut task = task.unwrap();
- info!(target: "tau", "Save the task: ref: {}", task.ref_id);
- task.workspace = workspace.clone();
- if piped {
- // if we can't load the task then it's a new task.
- // otherwise it's a modification.
- match TaskInfo::load(&task.ref_id, datastore_path) {
- Ok(loaded_task) => {
- let loaded_events = loaded_task.events.0;
- let mut events = task.events.0.clone();
- events.retain(|ev| !loaded_events.contains(ev));
- let file = "/tmp/tau_pipe";
- let mut pipe_write = pipe_write(file)?;
- let mut task_clone = task.clone();
- task_clone.events.0 = events;
- let json = serde_json::to_string(&task_clone).unwrap();
- pipe_write.write_all(json.as_bytes())?;
- }
- Err(_) => {
- let file = "/tmp/tau_pipe";
- let mut pipe_write = pipe_write(file)?;
- let mut task_clone = task.clone();
- task_clone.events.0.push(TaskEvent::new(
- "add_task".to_string(),
- task_clone.owner.clone(),
- "".to_string(),
- ));
- let json = serde_json::to_string(&task_clone).unwrap();
- pipe_write.write_all(json.as_bytes())?;
- }
- }
- }
- task.save(datastore_path)?;
- }
- Ok(())
- }
- async_daemonize!(realmain);
- async fn realmain(settings: Args, executor: Arc<smol::Executor<'_>>) -> Result<()> {
- let datastore_path = expand_path(&settings.datastore)?;
- let nickname =
- if settings.nickname.is_some() { settings.nickname.clone() } else { env::var("USER").ok() };
- if settings.refresh {
- println!("Removing local data in: {:?} (yes/no)? ", datastore_path);
- let mut confirm = String::new();
- stdin().read_line(&mut confirm).expect("Failed to read line");
- let confirm = confirm.to_lowercase();
- let confirm = confirm.trim();
- if confirm == "yes" || confirm == "y" {
- remove_dir_all(datastore_path).unwrap_or(());
- println!("Local data removed successfully.");
- } else {
- error!("Unexpected Value: {}", confirm);
- }
- return Ok(())
- }
- if nickname.is_none() {
- error!("Provide a nickname in config file");
- return Ok(())
- }
- if settings.piped {
- let file = "/tmp/tau_pipe";
- let path = CString::new(file).unwrap();
- unsafe { mkfifo(path.as_ptr(), 0o644) };
- }
- // mkdir datastore_path if not exists
- create_dir_all(datastore_path.clone())?;
- create_dir_all(datastore_path.join("month"))?;
- create_dir_all(datastore_path.join("task"))?;
- if settings.generate {
- println!("Generating a new workspace");
- loop {
- println!("Name for the new workspace: ");
- let mut workspace = String::new();
- stdin().read_line(&mut workspace).expect("Failed to read line");
- let workspace = workspace.to_lowercase();
- let workspace = workspace.trim();
- if workspace.is_empty() && workspace.len() < 3 {
- error!("Wrong workspace try again");
- continue
- }
- let secret_key = SecretKey::generate(&mut OsRng);
- let encoded = bs58::encode(secret_key.as_bytes());
- println!("workspace: {}:{}", workspace, encoded.into_string());
- println!("Please add it to the config file.");
- break
- }
- return Ok(())
- }
- let workspaces = get_workspaces(&settings)?;
- if workspaces.is_empty() {
- error!("Please add at least one workspace to the config file.");
- println!("Run `$ taud --generate` to generate new workspace.");
- return Ok(())
- }
- ////////////////////
- // Initialize the base structures
- ////////////////////
- let events_queue = EventsQueue::<EncryptedTask>::new();
- let model = Arc::new(Mutex::new(Model::new(events_queue.clone())));
- let view = Arc::new(Mutex::new(View::new(events_queue)));
- let model_clone = model.clone();
- ////////////////////
- // Buffers
- ////////////////////
- let seen_event = Seen::new();
- let seen_inv = Seen::new();
- let (broadcast_snd, broadcast_rcv) = smol::channel::unbounded::<TaskInfo>();
- //
- // P2p setup
- //
- let net_settings = settings.net.clone();
- let p2p = net::P2p::new(net_settings.into()).await;
- let registry = p2p.protocol_registry();
- registry
- .register(net::SESSION_ALL, move |channel, p2p| {
- let seen_event = seen_event.clone();
- let seen_inv = seen_inv.clone();
- let model = model.clone();
- async move { ProtocolEvent::init(channel, p2p, model, seen_event, seen_inv).await }
- })
- .await;
- p2p.clone().start(executor.clone()).await?;
- executor.spawn(p2p.clone().run(executor.clone())).detach();
- ////////////////////
- // Listner
- ////////////////////
- let seen_ids = Seen::new();
- let missed_events = Arc::new(Mutex::new(vec![]));
- executor
- .spawn(start_sync_loop(
- broadcast_rcv,
- view,
- model_clone,
- seen_ids,
- workspaces.clone(),
- datastore_path.clone(),
- missed_events,
- settings.piped,
- p2p.clone(),
- ))
- .detach();
- //
- // RPC interface
- //
- let rpc_interface = Arc::new(JsonRpcInterface::new(
- datastore_path.clone(),
- broadcast_snd,
- nickname.unwrap(),
- workspaces.clone(),
- p2p.clone(),
- ));
- let _ex = executor.clone();
- executor.spawn(listen_and_serve(settings.rpc_listen.clone(), rpc_interface, _ex)).detach();
- // Signal handling for graceful termination.
- let (signals_handler, signals_task) = SignalHandler::new()?;
- signals_handler.wait_termination(signals_task).await?;
- info!("Caught termination signal, cleaning up and exiting...");
- p2p.stop().await;
- Ok(())
- }
|