From 37ae67bd1c1e4355b6f5c43716becaab41f5df47 Mon Sep 17 00:00:00 2001 From: plazmoid Date: Thu, 19 Aug 2021 15:44:57 +0500 Subject: [PATCH] moved message structs to its own dir --- .gitignore | 3 +- Makefile | 8 +-- bin/u_agent/src/lib.rs | 2 - certs/gen_certs.sh | 13 ++-- integration/Cargo.toml | 3 +- integration/tests/behaviour.rs | 45 ++++++++++++ integration/tests/fixtures/agent.rs | 36 ++++++++++ integration/tests/fixtures/mod.rs | 1 + integration/tests/helpers/client.rs | 48 ------------- integration/tests/tests.rs | 69 ++----------------- lib/u_lib/src/api.rs | 27 +++++--- lib/u_lib/src/builder.rs | 21 +++--- .../src/{messaging.rs => messaging/base.rs} | 0 lib/u_lib/src/messaging/files.rs | 8 +++ lib/u_lib/src/messaging/mod.rs | 29 ++++++++ lib/u_lib/src/models/jobs/meta.rs | 15 ++-- lib/u_lib/src/models/mod.rs | 26 ++----- lib/u_lib/src/models/result.rs | 9 --- lib/u_lib/src/utils/hexlify.rs | 12 ++++ lib/u_lib/src/utils/mod.rs | 14 ++-- lib/u_lib/src/utils/storage.rs | 39 +++++++++++ 21 files changed, 240 insertions(+), 188 deletions(-) create mode 100644 integration/tests/fixtures/agent.rs create mode 100644 integration/tests/fixtures/mod.rs delete mode 100644 integration/tests/helpers/client.rs rename lib/u_lib/src/{messaging.rs => messaging/base.rs} (100%) create mode 100644 lib/u_lib/src/messaging/files.rs create mode 100644 lib/u_lib/src/messaging/mod.rs delete mode 100644 lib/u_lib/src/models/result.rs create mode 100644 lib/u_lib/src/utils/hexlify.rs create mode 100644 lib/u_lib/src/utils/storage.rs diff --git a/.gitignore b/.gitignore index 0f6b5f5..67f21e2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ data/ certs/* *.log echoer -.env.private \ No newline at end of file +.env.private +*.lock \ No newline at end of file diff --git a/Makefile b/Makefile index 969e896..ea72f7c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: _pre_build debug release run clean unit-tests integration-tests test +.PHONY: _pre_build debug release run clean unit integration test CARGO=./scripts/cargo_musl.sh @@ -17,10 +17,10 @@ release: _pre_build run: build ${CARGO} run -unit-tests: +unit: ${CARGO} test --lib -integration-tests: +integration: cd ./integration && ./integration_tests.sh -test: unit-tests integration-tests \ No newline at end of file +test: unit integration \ No newline at end of file diff --git a/bin/u_agent/src/lib.rs b/bin/u_agent/src/lib.rs index 5db6b54..fefe9dc 100644 --- a/bin/u_agent/src/lib.rs +++ b/bin/u_agent/src/lib.rs @@ -2,8 +2,6 @@ // поддержка питона // резолв адреса управляющего сервера через DoT // кроссплатформенность (реализовать интерфейс для винды и никсов) -// проверка обнов -// самоуничтожение #[macro_use] extern crate log; diff --git a/certs/gen_certs.sh b/certs/gen_certs.sh index a0e9d52..f1e9d6f 100755 --- a/certs/gen_certs.sh +++ b/certs/gen_certs.sh @@ -1,17 +1,18 @@ set -ex DIR=. -V3_CFG=v3.ext +V3_CFG=$DIR/v3.ext -cat > $DIR/$V3_CFG << EOF +cat > $V3_CFG << EOF authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign EOF - openssl req -x509 -newkey rsa:4096 -keyout $DIR/ca.key -out $DIR/ca.crt -nodes -days 365 -subj "/CN=root" openssl req -newkey rsa:4096 -keyout $DIR/alice.key -out $DIR/alice.csr -nodes -days 365 -subj "/CN=alice" openssl req -newkey rsa:4096 -keyout $DIR/server.key -out $DIR/server.csr -nodes -days 365 -subj "/CN=u_server" -openssl x509 -req -in $DIR/alice.csr -CA $DIR/ca.crt -CAkey $DIR/ca.key -out $DIR/alice.crt -set_serial 01 -days 365 -extfile $DIR/$V3_CFG -openssl x509 -req -in $DIR/server.csr -CA $DIR/ca.crt -CAkey $DIR/ca.key -out $DIR/server.crt -set_serial 01 -days 365 -extfile $DIR/$V3_CFG -openssl pkcs12 -export -out $DIR/alice.p12 -inkey $DIR/alice.key -in $DIR/alice.crt -passin pass: -passout pass: \ No newline at end of file +openssl x509 -req -in $DIR/alice.csr -CA $DIR/ca.crt -CAkey $DIR/ca.key -out $DIR/alice.crt -set_serial 01 -days 365 -extfile $V3_CFG +openssl x509 -req -in $DIR/server.csr -CA $DIR/ca.crt -CAkey $DIR/ca.key -out $DIR/server.crt -set_serial 01 -days 365 -extfile $V3_CFG +openssl pkcs12 -export -out $DIR/alice.p12 -inkey $DIR/alice.key -in $DIR/alice.crt -passin pass: -passout pass: + +rm $V3_CFG \ No newline at end of file diff --git a/integration/Cargo.toml b/integration/Cargo.toml index 28028a2..5bffbf6 100644 --- a/integration/Cargo.toml +++ b/integration/Cargo.toml @@ -16,6 +16,7 @@ serde_json = "1.0" serde = { version = "1.0.114", features = ["derive"] } futures = "0.3.5" shlex = "1.0.0" +rstest = "0.11" [dependencies.u_lib] path = "../lib/u_lib" @@ -24,4 +25,4 @@ version = "*" [[test]] name = "integration" -path = "tests/tests.rs" \ No newline at end of file +path = "tests/tests.rs" diff --git a/integration/tests/behaviour.rs b/integration/tests/behaviour.rs index 8b13789..6139d5e 100644 --- a/integration/tests/behaviour.rs +++ b/integration/tests/behaviour.rs @@ -1 +1,46 @@ +use crate::fixtures::agent::*; +use crate::helpers::Panel; +use rstest::rstest; +use std::error::Error; +use std::thread::sleep; +use std::time::Duration; +use u_lib::models::*; +use uuid::Uuid; + +type TestResult = Result>; + +#[rstest] +#[tokio::test] +async fn test_registration(#[future] register_agent: RegisteredAgent) -> TestResult { + let agent = register_agent.await; + let agents: Vec = Panel::check_output("agents list"); + let found = agents.iter().find(|v| v.id == agent.uid); + assert!(found.is_some()); + //teardown + Panel::check_status::(&format!("agents delete {}", agent.uid)); + Ok(()) //TODO: ______^^^^^ REMOV +} + +#[tokio::test] +async fn test_setup_tasks() -> TestResult { + //some independent agents should present + let agents: Vec = Panel::check_output("agents list"); + let agent_uid = agents[0].id; + let job_alias = "passwd_contents"; + let cmd = format!("jobs add --alias {} 'cat /etc/passwd'", job_alias); + Panel::check_status::(&cmd); + let cmd = format!("jobmap add {} {}", agent_uid, job_alias); + let assigned_uids: Vec = Panel::check_output(cmd); + for _ in 0..3 { + let result: Vec = + Panel::check_output(format!("jobmap list {}", assigned_uids[0])); + if result[0].state == JobState::Finished { + return Ok(()); + } else { + sleep(Duration::from_secs(5)); + eprintln!("waiting for task"); + } + } + panic!("Job didn't appear in the job map"); +} diff --git a/integration/tests/fixtures/agent.rs b/integration/tests/fixtures/agent.rs new file mode 100644 index 0000000..928b661 --- /dev/null +++ b/integration/tests/fixtures/agent.rs @@ -0,0 +1,36 @@ +use u_lib::{api::ClientHandler, models::*}; +use uuid::Uuid; + +pub struct RegisteredAgent { + pub uid: Uuid, +} + +impl RegisteredAgent { + pub async fn unregister(self) { + let cli = ClientHandler::new(None); + cli.del(Some(self.uid)).await.unwrap(); + } +} + +#[fixture] +pub async fn register_agent() -> RegisteredAgent { + let cli = ClientHandler::new(None); + let agent_uid = Uuid::new_v4(); + let resp = cli + .get_personal_jobs(Some(agent_uid)) + .await + .unwrap() + .pop() + .unwrap(); + let job_id = resp.job_id; + let resp = cli.get_jobs(Some(job_id)).await.unwrap().pop().unwrap(); + assert_eq!(resp.alias, Some("agent_hello".to_string())); + let agent_data = Agent { + id: agent_uid, + ..Default::default() + }; + cli.report(&vec![ExecResult::Agent(agent_data)]) + .await + .unwrap(); + RegisteredAgent { uid: agent_uid } +} diff --git a/integration/tests/fixtures/mod.rs b/integration/tests/fixtures/mod.rs new file mode 100644 index 0000000..f17bc55 --- /dev/null +++ b/integration/tests/fixtures/mod.rs @@ -0,0 +1 @@ +pub mod agent; diff --git a/integration/tests/helpers/client.rs b/integration/tests/helpers/client.rs deleted file mode 100644 index 01c5c89..0000000 --- a/integration/tests/helpers/client.rs +++ /dev/null @@ -1,48 +0,0 @@ -use reqwest::{Client, RequestBuilder, Url}; -use serde::Serialize; -use serde_json::{from_str, json, Value}; - -const SERVER: &str = "u_server"; -const PORT: &str = "63714"; - -pub struct AgentClient { - client: Client, - base_url: Url, -} - -impl AgentClient { - pub fn new() -> Self { - Self { - client: Client::new(), - base_url: Url::parse(&format!("http://{}:{}", SERVER, PORT)).unwrap(), - } - } - - async fn process_request(&self, req: RequestBuilder, resp_needed: bool) -> Value { - let resp = req.send().await.unwrap(); - if let Err(e) = resp.error_for_status_ref() { - panic!( - "Server responded with code {}\nError: {}", - e.status() - .map(|s| s.to_string()) - .unwrap_or(String::from("")), - e.to_string() - ); - } - if !resp_needed { - return json!([]); - } - let resp: Value = from_str(&resp.text().await.unwrap()).unwrap(); - resp.get("inner").unwrap().get(0).unwrap().clone() - } - - pub async fn get>(&self, url: S) -> Value { - let req = self.client.get(self.base_url.join(url.as_ref()).unwrap()); - self.process_request(req, true).await - } - - pub async fn post, B: Serialize>(&self, url: S, body: &B) -> Value { - let req = self.client.post(self.base_url.join(url.as_ref()).unwrap()); - self.process_request(req.json(body), false).await - } -} diff --git a/integration/tests/tests.rs b/integration/tests/tests.rs index 636a4be..7ef28c7 100644 --- a/integration/tests/tests.rs +++ b/integration/tests/tests.rs @@ -1,67 +1,6 @@ +mod behaviour; +mod fixtures; mod helpers; -use helpers::Panel; - -use std::error::Error; -use std::thread::sleep; -use std::time::Duration; -use u_lib::{api::ClientHandler, models::*}; -use uuid::Uuid; - -type TestResult = Result>; - -async fn register_agent() -> Uuid { - let cli = ClientHandler::new(None); - let agent_uid = Uuid::new_v4(); - let resp = cli - .get_personal_jobs(Some(agent_uid)) - .await - .unwrap() - .pop() - .unwrap(); - let job_id = resp.job_id; - let resp = cli.get_jobs(Some(job_id)).await.unwrap().pop().unwrap(); - assert_eq!(resp.alias, Some("agent_hello".to_string())); - let agent_data = Agent { - id: agent_uid, - ..Default::default() - }; - cli.report(&vec![ExecResult::Agent(agent_data)]) - .await - .unwrap(); - agent_uid -} - -#[tokio::test] -async fn test_registration() -> TestResult { - let agent_uid = register_agent().await; - let agents: Vec = Panel::check_output("agents list"); - let found = agents.iter().find(|v| v.id == agent_uid); - assert!(found.is_some()); - //teardown - Panel::check_status::(&format!("agents delete {}", agent_uid)); - Ok(()) -} - -#[tokio::test] -async fn test_setup_tasks() -> TestResult { - //some independent agents should present - let agents: Vec = Panel::check_output("agents list"); - let agent_uid = agents[0].id; - let job_alias = "passwd_contents"; - let cmd = format!("jobs add --alias {} 'cat /etc/passwd'", job_alias); - Panel::check_status::(&cmd); - let cmd = format!("jobmap add {} {}", agent_uid, job_alias); - let assigned_uids: Vec = Panel::check_output(cmd); - for _ in 0..3 { - let result: Vec = - Panel::check_output(format!("jobmap list {}", assigned_uids[0])); - if result[0].state == JobState::Finished { - return Ok(()); - } else { - sleep(Duration::from_secs(5)); - eprintln!("waiting for task"); - } - } - panic!() -} +#[macro_use] +extern crate rstest; diff --git a/lib/u_lib/src/api.rs b/lib/u_lib/src/api.rs index 82ba4bf..763f5f7 100644 --- a/lib/u_lib/src/api.rs +++ b/lib/u_lib/src/api.rs @@ -1,4 +1,5 @@ -#[allow(non_upper_case_globals)] +use crate::messaging; +//#[allow(non_upper_case_globals)] use crate::{ config::{MASTER_PORT, MASTER_SERVER}, messaging::{AsMsg, BaseMessage}, @@ -61,34 +62,42 @@ impl ClientHandler { // // get jobs for client #[api_route("GET")] - fn get_personal_jobs(&self, url_param: Option) -> VecDisplay {} + async fn get_personal_jobs(&self, url_param: Option) -> VecDisplay {} // // send something to server #[api_route("POST")] - fn report(&self, payload: &M) -> models::Empty {} + async fn report(&self, payload: &M) -> messaging::Empty {} + // + // download file + #[api_route("GET")] + async fn dl(&self, url_param: Option) -> Vec {} + // + // request download + #[api_route("POST")] + async fn dlr(&self, url_param: Option) -> messaging::DownloadInfo {} //##########// Admin area //##########// /// client listing #[api_route("GET")] - fn get_agents(&self, url_param: Option) -> VecDisplay {} + async fn get_agents(&self, url_param: Option) -> VecDisplay {} // // get all available jobs #[api_route("GET")] - fn get_jobs(&self, url_param: Option) -> VecDisplay {} + async fn get_jobs(&self, url_param: Option) -> VecDisplay {} // // create and upload job #[api_route("POST")] - fn upload_jobs(&self, payload: &[models::JobMeta]) -> models::Empty {} + async fn upload_jobs(&self, payload: &[models::JobMeta]) -> messaging::Empty {} // // delete something #[api_route("GET")] - fn del(&self, url_param: Option) -> i32 {} + async fn del(&self, url_param: Option) -> i32 {} // // set jobs for any client #[api_route("POST")] - fn set_jobs(&self, url_param: Option, payload: &[String]) -> VecDisplay {} + async fn set_jobs(&self, url_param: Option, payload: &[String]) -> VecDisplay {} // // get jobs for any client #[api_route("GET")] - fn get_agent_jobs(&self, url_param: Option) -> VecDisplay {} + async fn get_agent_jobs(&self, url_param: Option) -> VecDisplay {} } diff --git a/lib/u_lib/src/builder.rs b/lib/u_lib/src/builder.rs index da94225..5657745 100644 --- a/lib/u_lib/src/builder.rs +++ b/lib/u_lib/src/builder.rs @@ -1,15 +1,13 @@ use crate::{ - cache::JobCache, - executor::{Waiter, DynFut}, - models::{Agent, AssignedJob, JobMeta, JobType, ExecResult}, - utils::{CombinedResult, OneOrVec}, - UError, + UError, UResult, cache::JobCache, executor::{Waiter, DynFut}, + models::{Agent, AssignedJob, JobMeta, JobType, ExecResult}, + utils::{CombinedResult, OneOrVec} }; use guess_host_triple::guess_host_triple; use std::collections::HashMap; pub struct JobBuilder { - jobs: Waiter, + waiter: Waiter, } impl JobBuilder { @@ -25,11 +23,12 @@ impl JobBuilder { } let job_meta = job_meta.unwrap(); //waiting for try-blocks stabilization - let built_req = (|| { + let built_req = (|| -> UResult<()> { Ok(match job_meta.exec_type { JobType::Shell => { let meta = JobCache::get(&req.job_id).ok_or(UError::NoJob(req.job_id))?; let curr_platform = guess_host_triple().unwrap_or("unknown").to_string(); + //extend platform checking (partial check) if meta.platform != curr_platform { return Err(UError::InsuitablePlatform( meta.platform.clone(), @@ -48,7 +47,7 @@ impl JobBuilder { } } result.ok(Self { - jobs: Waiter::new(prepared), + waiter: Waiter::new(prepared), }); result } @@ -68,18 +67,18 @@ impl JobBuilder { /// Spawn jobs and pop results later pub async fn spawn(mut self) -> Self { - self.jobs = self.jobs.spawn().await; + self.waiter = self.waiter.spawn().await; self } /// Spawn jobs and wait for result pub async fn wait(self) -> Vec { - self.jobs.spawn().await.wait().await + self.waiter.spawn().await.wait().await } /// Spawn one job and wait for result pub async fn wait_one(self) -> ExecResult { - self.jobs.spawn().await.wait().await.pop().unwrap() + self.waiter.spawn().await.wait().await.pop().unwrap() } } diff --git a/lib/u_lib/src/messaging.rs b/lib/u_lib/src/messaging/base.rs similarity index 100% rename from lib/u_lib/src/messaging.rs rename to lib/u_lib/src/messaging/base.rs diff --git a/lib/u_lib/src/messaging/files.rs b/lib/u_lib/src/messaging/files.rs new file mode 100644 index 0000000..4cdb804 --- /dev/null +++ b/lib/u_lib/src/messaging/files.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct DownloadInfo { + hashsum: String, + dl_fid: Uuid, +} diff --git a/lib/u_lib/src/messaging/mod.rs b/lib/u_lib/src/messaging/mod.rs new file mode 100644 index 0000000..12898ce --- /dev/null +++ b/lib/u_lib/src/messaging/mod.rs @@ -0,0 +1,29 @@ +mod base; +mod files; + +use crate::models::*; +pub use base::{AsMsg, BaseMessage}; +pub use files::*; +use serde::{Deserialize, Serialize}; +use std::fmt; +use uuid::Uuid; + +impl AsMsg for Agent {} +impl AsMsg for AssignedJob {} +impl AsMsg for DownloadInfo {} +impl AsMsg for ExecResult {} +impl AsMsg for JobMeta {} +impl AsMsg for String {} +impl AsMsg for Uuid {} +impl AsMsg for Empty {} +impl AsMsg for i32 {} +impl AsMsg for u8 {} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +pub struct Empty; + +impl fmt::Display for Empty { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "") + } +} diff --git a/lib/u_lib/src/models/jobs/meta.rs b/lib/u_lib/src/models/jobs/meta.rs index 50e1b65..a0d9d8a 100644 --- a/lib/u_lib/src/models/jobs/meta.rs +++ b/lib/u_lib/src/models/jobs/meta.rs @@ -41,12 +41,12 @@ impl fmt::Display for JobMeta { out += &format!("\nPlatform: {}", self.platform); if self.exec_type == JobType::Shell && self.payload.is_some() { let payload = self.payload.as_ref().unwrap(); - let pld_len = { + let (pld_len, large) = { let pl = payload.len(); if pl > 20 { - 20 + (20, true) } else { - pl + (pl, false) } }; let pld_beginning = payload @@ -57,7 +57,7 @@ impl fmt::Display for JobMeta { out += &format!( "\nPayload: {}{}", String::from_utf8_lossy(&pld_beginning), - if pld_len <= 20 { "" } else { " <...>" } + if large { "" } else { " <...>" } ); } write!(f, "{}", out) @@ -69,7 +69,7 @@ impl Default for JobMeta { Self { id: Uuid::new_v4(), alias: None, - argv: String::from("/bin/bash -c {}"), + argv: String::new(), exec_type: JobType::Shell, platform: guess_host_triple().unwrap_or("unknown").to_string(), payload: None, @@ -111,9 +111,12 @@ impl JobMetaBuilder { } pub fn build(self) -> UResult { - let inner = self.inner; + let mut inner = self.inner; match inner.exec_type { JobType::Shell => { + if inner.argv == "" { + inner.argv = String::from("/bin/bash -c {}") + } let argv_parts = shlex::split(&inner.argv).ok_or(UError::JobArgsError("Shlex failed".into()))?; let empty_err = UError::JobArgsError("Empty argv".into()); diff --git a/lib/u_lib/src/models/mod.rs b/lib/u_lib/src/models/mod.rs index 29b14de..d8092f3 100644 --- a/lib/u_lib/src/models/mod.rs +++ b/lib/u_lib/src/models/mod.rs @@ -1,29 +1,13 @@ mod agent; pub mod jobs; -mod result; pub mod schema; -use crate::messaging::AsMsg; -pub use crate::models::result::ExecResult; pub use crate::models::{agent::*, jobs::*}; use serde::{Deserialize, Serialize}; -use std::fmt; -use uuid::Uuid; -impl AsMsg for Agent {} -impl AsMsg for AssignedJob {} -impl AsMsg for ExecResult {} -impl AsMsg for JobMeta {} -impl AsMsg for String {} -impl AsMsg for Uuid {} -impl AsMsg for Empty {} -impl AsMsg for i32 {} - -#[derive(Serialize, Deserialize, Clone, Default, Debug)] -pub struct Empty; - -impl fmt::Display for Empty { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "") - } +#[derive(Serialize, Deserialize, Clone, PartialEq)] +pub enum ExecResult { + Assigned(AssignedJob), + Agent(Agent), + Dummy, } diff --git a/lib/u_lib/src/models/result.rs b/lib/u_lib/src/models/result.rs deleted file mode 100644 index 849405a..0000000 --- a/lib/u_lib/src/models/result.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::models::{Agent, AssignedJob}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, PartialEq)] -pub enum ExecResult { - Assigned(AssignedJob), - Agent(Agent), - Dummy, -} diff --git a/lib/u_lib/src/utils/hexlify.rs b/lib/u_lib/src/utils/hexlify.rs new file mode 100644 index 0000000..d3a0d14 --- /dev/null +++ b/lib/u_lib/src/utils/hexlify.rs @@ -0,0 +1,12 @@ +use std::fmt; + +pub struct Hexlify<'b>(pub &'b [u8]); + +impl<'a> fmt::LowerHex for Hexlify<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for byte in self.0.iter() { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} diff --git a/lib/u_lib/src/utils/mod.rs b/lib/u_lib/src/utils/mod.rs index 13f94d3..0082b16 100644 --- a/lib/u_lib/src/utils/mod.rs +++ b/lib/u_lib/src/utils/mod.rs @@ -1,11 +1,15 @@ -pub mod combined_result; -pub mod conv; -pub mod misc; -pub mod tempfile; -pub mod vec_display; +mod combined_result; +mod conv; +mod hexlify; +mod misc; +mod storage; +mod tempfile; +mod vec_display; pub use combined_result::*; pub use conv::*; +pub use hexlify::*; pub use misc::*; +pub use storage::*; pub use tempfile::*; pub use vec_display::*; diff --git a/lib/u_lib/src/utils/storage.rs b/lib/u_lib/src/utils/storage.rs new file mode 100644 index 0000000..d96faba --- /dev/null +++ b/lib/u_lib/src/utils/storage.rs @@ -0,0 +1,39 @@ +use once_cell::sync::Lazy; +use std::cmp::Eq; +use std::collections::HashMap; +use std::hash::Hash; +use std::ops::Deref; +use std::sync::Arc; +use std::sync::{Mutex, MutexGuard}; + +//improve this later, replace job cacher with it +//possibly add different backends (memory, disk) +pub struct SharedStorage(Arc>>); + +impl SharedStorage { + pub fn new() -> Lazy> { + Lazy::new(|| SharedStorage(Arc::new(Mutex::new(HashMap::new())))) + } + + pub fn lock(&self) -> MutexGuard<'_, HashMap> { + self.0.lock().unwrap() + } + + pub fn get<'get, 'slf: 'get>(&'slf self, key: &'get Key) -> Option> { + if !self.lock().contains_key(key) { + return None; + } + let lock = self.lock(); + Some(RefHolder(lock, key)) + } +} + +pub struct RefHolder<'h, Key, Val>(pub MutexGuard<'h, HashMap>, pub &'h Key); + +impl<'h, Key: Eq + Hash, Val> Deref for RefHolder<'h, Key, Val> { + type Target = Val; + + fn deref(&self) -> &Self::Target { + self.0.get(self.1).unwrap() + } +}