mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
research/raft-tool: decrypt tasks in tau datastore using secret key
This commit is contained in:
@@ -12,4 +12,17 @@ features = ["raft"]
|
||||
async-std = "1.11.0"
|
||||
sled = "0.34.7"
|
||||
|
||||
# for taud
|
||||
crypto_box = {version = "0.7.2", features = ["std"]}
|
||||
hex = "0.4.3"
|
||||
log = "0.4.17"
|
||||
simplelog = "0.12.0"
|
||||
rand = "0.8.5"
|
||||
chrono = "0.4.19"
|
||||
url = "2.2.2"
|
||||
serde = {version = "1.0.137", features = ["derive"]}
|
||||
serde_json = "1.0.81"
|
||||
thiserror = "1.0.31"
|
||||
|
||||
|
||||
[workspace]
|
||||
|
||||
45
script/research/raft-tool/src/error.rs
Normal file
45
script/research/raft-tool/src/error.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use serde_json::Value;
|
||||
|
||||
use darkfi::rpc::jsonrpc::{ErrorCode, JsonError, JsonResponse, JsonResult};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum TaudError {
|
||||
#[error("Due timestamp invalid")]
|
||||
InvalidDueTime,
|
||||
#[error("Invalid Id")]
|
||||
InvalidId,
|
||||
#[error("Invalid Data/Params: `{0}` ")]
|
||||
InvalidData(String),
|
||||
#[error("InternalError")]
|
||||
Darkfi(#[from] darkfi::error::Error),
|
||||
#[error("Json serialization error: `{0}`")]
|
||||
SerdeJsonError(String),
|
||||
}
|
||||
|
||||
pub type TaudResult<T> = std::result::Result<T, TaudError>;
|
||||
|
||||
impl From<serde_json::Error> for TaudError {
|
||||
fn from(err: serde_json::Error) -> TaudError {
|
||||
TaudError::SerdeJsonError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_json_result(res: TaudResult<Value>, id: Value) -> JsonResult {
|
||||
match res {
|
||||
Ok(v) => JsonResponse::new(v, id).into(),
|
||||
Err(err) => match err {
|
||||
TaudError::InvalidId => {
|
||||
JsonError::new(ErrorCode::InvalidParams, Some("invalid task id".into()), id).into()
|
||||
}
|
||||
TaudError::InvalidData(e) | TaudError::SerdeJsonError(e) => {
|
||||
JsonError::new(ErrorCode::InvalidParams, Some(e), id).into()
|
||||
}
|
||||
TaudError::InvalidDueTime => {
|
||||
JsonError::new(ErrorCode::InvalidParams, Some("invalid due time".into()), id).into()
|
||||
}
|
||||
TaudError::Darkfi(e) => {
|
||||
JsonError::new(ErrorCode::InternalError, Some(e.to_string()), id).into()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
use crypto_box::{aead::Aead, Box, SecretKey, KEY_SIZE};
|
||||
|
||||
use darkfi::{
|
||||
raft::DataStore,
|
||||
util::{
|
||||
@@ -9,6 +11,13 @@ use darkfi::{
|
||||
Result,
|
||||
};
|
||||
|
||||
mod error;
|
||||
mod month_tasks;
|
||||
mod task_info;
|
||||
mod util;
|
||||
|
||||
use crate::{task_info::TaskInfo, util::load};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Info<T> {
|
||||
pub logs: Vec<Log<T>>,
|
||||
@@ -32,6 +41,19 @@ struct EncryptedTask {
|
||||
payload: Vec<u8>,
|
||||
}
|
||||
|
||||
fn decrypt_task(encrypt_task: &EncryptedTask, secret_key: &SecretKey) -> Option<TaskInfo> {
|
||||
let public_key = secret_key.public_key();
|
||||
let msg_box = Box::new(&public_key, secret_key);
|
||||
|
||||
let nonce = encrypt_task.nonce.as_slice();
|
||||
let decrypted_task = match msg_box.decrypt(nonce.into(), &encrypt_task.payload[..]) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
deserialize(&decrypted_task).ok()
|
||||
}
|
||||
|
||||
type PrivmsgId = u32;
|
||||
|
||||
#[derive(Debug, SerialEncodable, SerialDecodable)]
|
||||
@@ -46,6 +68,24 @@ fn extract_taud() -> Result<String> {
|
||||
let db_path = expand_path(&"~/.config/darkfi/tau/tau.db").unwrap();
|
||||
let datastore = DataStore::<EncryptedTask>::new(&db_path.to_str().unwrap())?;
|
||||
|
||||
let sk_path = expand_path(&"~/.config/darkfi/tau/secret_key").unwrap();
|
||||
|
||||
let sk = {
|
||||
let loaded_key = load::<String>(&sk_path);
|
||||
|
||||
if loaded_key.is_err() {
|
||||
log::error!(
|
||||
"Could not load secret key from file, \
|
||||
please run \"taud --help\" for more information"
|
||||
);
|
||||
return Ok("Load secret_key error".into())
|
||||
}
|
||||
|
||||
let sk_bytes = hex::decode(loaded_key.unwrap())?;
|
||||
let sk_bytes: [u8; KEY_SIZE] = sk_bytes.as_slice().try_into()?;
|
||||
SecretKey::try_from(sk_bytes)?
|
||||
};
|
||||
|
||||
println!("Extracting db from: {:?}", db_path);
|
||||
|
||||
// Retrieve all data trees
|
||||
@@ -60,14 +100,20 @@ fn extract_taud() -> Result<String> {
|
||||
// Logs
|
||||
let mut logs = vec![];
|
||||
for log in sled_logs {
|
||||
logs.push(Log { term: log.term, msg: deserialize(&log.msg)? });
|
||||
let encrypt_task: EncryptedTask = deserialize(&log.msg)?;
|
||||
let task_info = decrypt_task(&encrypt_task, &sk).unwrap();
|
||||
logs.push(Log { term: log.term, msg: task_info });
|
||||
}
|
||||
|
||||
// Commits
|
||||
let mut commits = vec![];
|
||||
for commit in &sled_commits {
|
||||
commits
|
||||
.push(EncryptedTask { nonce: commit.nonce.clone(), payload: commit.payload.clone() });
|
||||
let task_info = decrypt_task(
|
||||
&EncryptedTask { nonce: commit.nonce.clone(), payload: commit.payload.clone() },
|
||||
&sk,
|
||||
)
|
||||
.unwrap();
|
||||
commits.push(task_info);
|
||||
}
|
||||
|
||||
// Voted for
|
||||
@@ -85,7 +131,7 @@ fn extract_taud() -> Result<String> {
|
||||
terms.push(term);
|
||||
}
|
||||
|
||||
let info = Info::<EncryptedTask> { logs, commits, voted_for, terms };
|
||||
let info = Info::<TaskInfo> { logs, commits, voted_for, terms };
|
||||
let info_string = format!("{:#?}", info);
|
||||
Ok(info_string)
|
||||
}
|
||||
|
||||
213
script/research/raft-tool/src/month_tasks.rs
Normal file
213
script/research/raft-tool/src/month_tasks.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
use std::{
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use chrono::{TimeZone, Utc};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use darkfi::util::Timestamp;
|
||||
|
||||
use crate::{
|
||||
error::{TaudError, TaudResult},
|
||||
task_info::TaskInfo,
|
||||
util::{load, save},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct MonthTasks {
|
||||
created_at: Timestamp,
|
||||
task_tks: Vec<String>,
|
||||
}
|
||||
|
||||
impl MonthTasks {
|
||||
pub fn new(task_tks: &[String]) -> Self {
|
||||
Self { created_at: Timestamp::current_time(), task_tks: task_tks.to_owned() }
|
||||
}
|
||||
|
||||
pub fn add(&mut self, ref_id: &str) {
|
||||
debug!(target: "tau", "MonthTasks::add()");
|
||||
if !self.task_tks.contains(&ref_id.into()) {
|
||||
self.task_tks.push(ref_id.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn objects(&self, dataset_path: &Path) -> TaudResult<Vec<TaskInfo>> {
|
||||
debug!(target: "tau", "MonthTasks::objects()");
|
||||
let mut tks: Vec<TaskInfo> = vec![];
|
||||
|
||||
for ref_id in self.task_tks.iter() {
|
||||
tks.push(TaskInfo::load(ref_id, dataset_path)?);
|
||||
}
|
||||
|
||||
Ok(tks)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, ref_id: &str) {
|
||||
debug!(target: "tau", "MonthTasks::remove()");
|
||||
if let Some(index) = self.task_tks.iter().position(|t| *t == ref_id) {
|
||||
self.task_tks.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_date(&mut self, date: &Timestamp) {
|
||||
debug!(target: "tau", "MonthTasks::set_date()");
|
||||
self.created_at = *date;
|
||||
}
|
||||
|
||||
fn get_path(date: &Timestamp, dataset_path: &Path) -> PathBuf {
|
||||
debug!(target: "tau", "MonthTasks::get_path()");
|
||||
dataset_path.join("month").join(Utc.timestamp(date.0, 0).format("%m%y").to_string())
|
||||
}
|
||||
|
||||
pub fn save(&self, dataset_path: &Path) -> TaudResult<()> {
|
||||
debug!(target: "tau", "MonthTasks::save()");
|
||||
save::<Self>(&Self::get_path(&self.created_at, dataset_path), self)
|
||||
.map_err(TaudError::Darkfi)
|
||||
}
|
||||
|
||||
fn get_all(dataset_path: &Path) -> io::Result<Vec<PathBuf>> {
|
||||
debug!(target: "tau", "MonthTasks::get_all()");
|
||||
|
||||
let mut entries = fs::read_dir(dataset_path.join("month"))?
|
||||
.map(|res| res.map(|e| e.path()))
|
||||
.collect::<Result<Vec<_>, io::Error>>()?;
|
||||
|
||||
entries.sort();
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn create(date: &Timestamp, dataset_path: &Path) -> TaudResult<Self> {
|
||||
debug!(target: "tau", "MonthTasks::create()");
|
||||
|
||||
let mut mt = Self::new(&[]);
|
||||
mt.set_date(date);
|
||||
mt.save(dataset_path)?;
|
||||
Ok(mt)
|
||||
}
|
||||
|
||||
pub fn load_or_create(date: Option<&Timestamp>, dataset_path: &Path) -> TaudResult<Self> {
|
||||
debug!(target: "tau", "MonthTasks::load_or_create()");
|
||||
|
||||
// if a date is given we load that date's month tasks
|
||||
// if not, we load tasks from all months
|
||||
match date {
|
||||
Some(date) => match load::<Self>(&Self::get_path(date, dataset_path)) {
|
||||
Ok(mt) => Ok(mt),
|
||||
Err(_) => Self::create(date, dataset_path),
|
||||
},
|
||||
None => {
|
||||
let path_all = match Self::get_all(dataset_path) {
|
||||
Ok(t) => t,
|
||||
Err(_) => vec![],
|
||||
};
|
||||
|
||||
let mut loaded_mt = Self::new(&[]);
|
||||
|
||||
for path in path_all {
|
||||
let mt = load::<Self>(&path)?;
|
||||
loaded_mt.created_at = mt.created_at;
|
||||
for tks in mt.task_tks {
|
||||
if !loaded_mt.task_tks.contains(&tks) {
|
||||
loaded_mt.task_tks.push(tks)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(loaded_mt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_current_open_tasks(dataset_path: &Path) -> TaudResult<Vec<TaskInfo>> {
|
||||
let mt = Self::load_or_create(None, dataset_path)?;
|
||||
Ok(mt.objects(dataset_path)?.into_iter().filter(|t| t.get_state() != "stop").collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
fs::{create_dir_all, remove_dir_all},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use darkfi::Result;
|
||||
|
||||
const TEST_DATA_PATH: &str = "/tmp/test_tau_data";
|
||||
|
||||
fn get_path() -> Result<PathBuf> {
|
||||
remove_dir_all(TEST_DATA_PATH).ok();
|
||||
|
||||
let path = PathBuf::from(TEST_DATA_PATH);
|
||||
|
||||
// mkdir dataset_path if not exists
|
||||
create_dir_all(path.join("month"))?;
|
||||
create_dir_all(path.join("task"))?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_and_save_tasks() -> TaudResult<()> {
|
||||
let dataset_path = get_path()?;
|
||||
|
||||
// load and save TaskInfo
|
||||
///////////////////////
|
||||
|
||||
let mut task =
|
||||
TaskInfo::new("test_title", "test_desc", "NICKNAME", None, 0.0, &dataset_path)?;
|
||||
|
||||
task.save(&dataset_path)?;
|
||||
|
||||
let t_load = TaskInfo::load(&task.ref_id, &dataset_path)?;
|
||||
|
||||
assert_eq!(task, t_load);
|
||||
|
||||
task.set_title("test_title_2");
|
||||
|
||||
task.save(&dataset_path)?;
|
||||
|
||||
let t_load = TaskInfo::load(&task.ref_id, &dataset_path)?;
|
||||
|
||||
assert_eq!(task, t_load);
|
||||
|
||||
// load and save MonthTasks
|
||||
///////////////////////
|
||||
|
||||
let task_tks = vec![];
|
||||
|
||||
let mut mt = MonthTasks::new(&task_tks);
|
||||
|
||||
mt.save(&dataset_path)?;
|
||||
|
||||
let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
|
||||
|
||||
assert_eq!(mt, mt_load);
|
||||
|
||||
mt.add(&task.ref_id);
|
||||
|
||||
mt.save(&dataset_path)?;
|
||||
|
||||
let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
|
||||
|
||||
assert_eq!(mt, mt_load);
|
||||
|
||||
// activate task
|
||||
///////////////////////
|
||||
|
||||
let task =
|
||||
TaskInfo::new("test_title_3", "test_desc", "NICKNAME", None, 0.0, &dataset_path)?;
|
||||
|
||||
task.save(&dataset_path)?;
|
||||
|
||||
let mt_load = MonthTasks::load_or_create(Some(&Timestamp::current_time()), &dataset_path)?;
|
||||
|
||||
assert!(mt_load.task_tks.contains(&task.ref_id));
|
||||
|
||||
remove_dir_all(TEST_DATA_PATH).ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
274
script/research/raft-tool/src/task_info.rs
Normal file
274
script/research/raft-tool/src/task_info.rs
Normal file
@@ -0,0 +1,274 @@
|
||||
use std::{
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use darkfi::util::{
|
||||
serial::{Decodable, Encodable, SerialDecodable, SerialEncodable, VarInt},
|
||||
Timestamp,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{TaudError, TaudResult},
|
||||
month_tasks::MonthTasks,
|
||||
util::{find_free_id, load, random_ref_id, save},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq)]
|
||||
struct TaskEvent {
|
||||
action: String,
|
||||
timestamp: Timestamp,
|
||||
}
|
||||
|
||||
impl TaskEvent {
|
||||
fn new(action: String) -> Self {
|
||||
Self { action, timestamp: Timestamp::current_time() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, SerialDecodable, SerialEncodable, PartialEq)]
|
||||
pub struct Comment {
|
||||
content: String,
|
||||
author: String,
|
||||
timestamp: Timestamp,
|
||||
}
|
||||
|
||||
impl Comment {
|
||||
pub fn new(content: &str, author: &str) -> Self {
|
||||
Self {
|
||||
content: content.into(),
|
||||
author: author.into(),
|
||||
timestamp: Timestamp::current_time(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TaskEvents(Vec<TaskEvent>);
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TaskComments(Vec<Comment>);
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TaskProjects(Vec<String>);
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TaskAssigns(Vec<String>);
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, SerialEncodable, SerialDecodable, PartialEq)]
|
||||
pub struct TaskInfo {
|
||||
pub(crate) ref_id: String,
|
||||
id: u32,
|
||||
title: String,
|
||||
desc: String,
|
||||
owner: String,
|
||||
assign: TaskAssigns,
|
||||
project: TaskProjects,
|
||||
due: Option<Timestamp>,
|
||||
rank: f32,
|
||||
created_at: Timestamp,
|
||||
events: TaskEvents,
|
||||
comments: TaskComments,
|
||||
}
|
||||
|
||||
impl TaskInfo {
|
||||
pub fn new(
|
||||
title: &str,
|
||||
desc: &str,
|
||||
owner: &str,
|
||||
due: Option<Timestamp>,
|
||||
rank: f32,
|
||||
dataset_path: &Path,
|
||||
) -> TaudResult<Self> {
|
||||
// generate ref_id
|
||||
let ref_id = random_ref_id();
|
||||
|
||||
let created_at = Timestamp::current_time();
|
||||
|
||||
let task_ids: Vec<u32> =
|
||||
MonthTasks::load_current_open_tasks(dataset_path)?.into_iter().map(|t| t.id).collect();
|
||||
|
||||
let id: u32 = find_free_id(&task_ids);
|
||||
|
||||
if let Some(d) = &due {
|
||||
if *d < Timestamp::current_time() {
|
||||
return Err(TaudError::InvalidDueTime)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
ref_id,
|
||||
id,
|
||||
title: title.into(),
|
||||
desc: desc.into(),
|
||||
owner: owner.into(),
|
||||
assign: TaskAssigns(vec![]),
|
||||
project: TaskProjects(vec![]),
|
||||
due,
|
||||
rank,
|
||||
created_at,
|
||||
comments: TaskComments(vec![]),
|
||||
events: TaskEvents(vec![]),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load(ref_id: &str, dataset_path: &Path) -> TaudResult<Self> {
|
||||
debug!(target: "tau", "TaskInfo::load()");
|
||||
let task = load::<Self>(&Self::get_path(ref_id, dataset_path))?;
|
||||
Ok(task)
|
||||
}
|
||||
|
||||
pub fn save(&self, dataset_path: &Path) -> TaudResult<()> {
|
||||
debug!(target: "tau", "TaskInfo::save()");
|
||||
save::<Self>(&Self::get_path(&self.ref_id, dataset_path), self)
|
||||
.map_err(TaudError::Darkfi)?;
|
||||
|
||||
if self.get_state() == "stop" {
|
||||
self.deactivate(dataset_path)?;
|
||||
} else {
|
||||
self.activate(dataset_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn activate(&self, path: &Path) -> TaudResult<()> {
|
||||
debug!(target: "tau", "TaskInfo::activate()");
|
||||
let mut mt = MonthTasks::load_or_create(Some(&self.created_at), path)?;
|
||||
mt.add(&self.ref_id);
|
||||
mt.save(path)
|
||||
}
|
||||
|
||||
pub fn deactivate(&self, path: &Path) -> TaudResult<()> {
|
||||
debug!(target: "tau", "TaskInfo::deactivate()");
|
||||
let mut mt = MonthTasks::load_or_create(Some(&self.created_at), path)?;
|
||||
mt.remove(&self.ref_id);
|
||||
mt.save(path)
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> String {
|
||||
debug!(target: "tau", "TaskInfo::get_state()");
|
||||
if let Some(ev) = self.events.0.last() {
|
||||
ev.action.clone()
|
||||
} else {
|
||||
"open".into()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path(ref_id: &str, dataset_path: &Path) -> PathBuf {
|
||||
debug!(target: "tau", "TaskInfo::get_path()");
|
||||
dataset_path.join("task").join(ref_id)
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> u32 {
|
||||
debug!(target: "tau", "TaskInfo::get_id()");
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn set_title(&mut self, title: &str) {
|
||||
debug!(target: "tau", "TaskInfo::set_title()");
|
||||
self.title = title.into();
|
||||
}
|
||||
|
||||
pub fn set_desc(&mut self, desc: &str) {
|
||||
debug!(target: "tau", "TaskInfo::set_desc()");
|
||||
self.desc = desc.into();
|
||||
}
|
||||
|
||||
pub fn set_assign(&mut self, assign: &[String]) {
|
||||
debug!(target: "tau", "TaskInfo::set_assign()");
|
||||
self.assign = TaskAssigns(assign.to_owned());
|
||||
}
|
||||
|
||||
pub fn set_project(&mut self, project: &[String]) {
|
||||
debug!(target: "tau", "TaskInfo::set_project()");
|
||||
self.project = TaskProjects(project.to_owned());
|
||||
}
|
||||
|
||||
pub fn set_comment(&mut self, c: Comment) {
|
||||
debug!(target: "tau", "TaskInfo::set_comment()");
|
||||
self.comments.0.push(c);
|
||||
}
|
||||
|
||||
pub fn set_rank(&mut self, r: f32) {
|
||||
debug!(target: "tau", "TaskInfo::set_rank()");
|
||||
self.rank = r;
|
||||
}
|
||||
|
||||
pub fn set_due(&mut self, d: Option<Timestamp>) {
|
||||
debug!(target: "tau", "TaskInfo::set_due()");
|
||||
self.due = d;
|
||||
}
|
||||
|
||||
pub fn set_state(&mut self, action: &str) {
|
||||
debug!(target: "tau", "TaskInfo::set_state()");
|
||||
if self.get_state() == action {
|
||||
return
|
||||
}
|
||||
self.events.0.push(TaskEvent::new(action.into()));
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for TaskEvents {
|
||||
fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
|
||||
encode_vec(&self.0, s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for TaskEvents {
|
||||
fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
|
||||
Ok(Self(decode_vec(d)?))
|
||||
}
|
||||
}
|
||||
impl Encodable for TaskComments {
|
||||
fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
|
||||
encode_vec(&self.0, s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for TaskComments {
|
||||
fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
|
||||
Ok(Self(decode_vec(d)?))
|
||||
}
|
||||
}
|
||||
impl Encodable for TaskProjects {
|
||||
fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
|
||||
encode_vec(&self.0, s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for TaskProjects {
|
||||
fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
|
||||
Ok(Self(decode_vec(d)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for TaskAssigns {
|
||||
fn encode<S: io::Write>(&self, s: S) -> darkfi::Result<usize> {
|
||||
encode_vec(&self.0, s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for TaskAssigns {
|
||||
fn decode<D: io::Read>(d: D) -> darkfi::Result<Self> {
|
||||
Ok(Self(decode_vec(d)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_vec<T: Encodable, S: io::Write>(vec: &[T], mut s: S) -> darkfi::Result<usize> {
|
||||
let mut len = 0;
|
||||
len += VarInt(vec.len() as u64).encode(&mut s)?;
|
||||
for c in vec.iter() {
|
||||
len += c.encode(&mut s)?;
|
||||
}
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
fn decode_vec<T: Decodable, D: io::Read>(mut d: D) -> darkfi::Result<Vec<T>> {
|
||||
let len = VarInt::decode(&mut d)?.0;
|
||||
let mut ret = Vec::with_capacity(len as usize);
|
||||
for _ in 0..len {
|
||||
ret.push(Decodable::decode(&mut d)?);
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
59
script/research/raft-tool/src/util.rs
Normal file
59
script/research/raft-tool/src/util.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use std::{fs::File, io::BufReader, path::Path};
|
||||
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
use darkfi::Result;
|
||||
|
||||
pub fn random_ref_id() -> String {
|
||||
thread_rng().sample_iter(&Alphanumeric).take(30).map(char::from).collect()
|
||||
}
|
||||
|
||||
pub fn find_free_id(task_ids: &[u32]) -> u32 {
|
||||
for i in 1.. {
|
||||
if !task_ids.contains(&i) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
pub fn load<T: DeserializeOwned>(path: &Path) -> Result<T> {
|
||||
let file = File::open(path)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
let value: T = serde_json::from_reader(reader)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn save<T: Serialize>(path: &Path, value: &T) -> Result<()> {
|
||||
let file = File::create(path)?;
|
||||
serde_json::to_writer_pretty(file, value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn find_free_id_test() -> Result<()> {
|
||||
let mut ids: Vec<u32> = vec![1, 3, 8, 9, 10, 3];
|
||||
let ids_empty: Vec<u32> = vec![];
|
||||
let ids_duplicate: Vec<u32> = vec![1; 100];
|
||||
|
||||
let find_id = find_free_id(&ids);
|
||||
|
||||
assert_eq!(find_id, 2);
|
||||
|
||||
ids.push(find_id);
|
||||
|
||||
assert_eq!(find_free_id(&ids), 4);
|
||||
|
||||
assert_eq!(find_free_id(&ids_empty), 1);
|
||||
|
||||
assert_eq!(find_free_id(&ids_duplicate), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user