use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use chrono::{NaiveDateTime, Utc};
use log::warn;
use memflow::plugins::plugin_analyzer;
use memflow::plugins::plugin_analyzer::PluginDescriptorInfo;
use parking_lot::{lock_api::RwLockReadGuard, RawRwLock, RwLock};
use serde::{Deserialize, Serialize};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use crate::{
error::{Error, Result},
pki::SignatureVerifier,
};
pub mod database;
use database::PluginDatabase;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadata {
pub digest: String,
pub signature: String,
pub created_at: NaiveDateTime,
pub descriptors: Vec<PluginDescriptorInfo>,
}
#[derive(Clone)]
pub struct Storage {
root: PathBuf,
database: Arc<RwLock<PluginDatabase>>,
signature_verifier: Option<SignatureVerifier>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum UploadResponse {
Added,
AlreadyExists,
}
impl Storage {
pub fn new<P: AsRef<Path>>(root: P) -> Result<Self> {
let mut database = PluginDatabase::new();
let paths = std::fs::read_dir(&root)?;
for path in paths.filter_map(|p| p.ok()) {
if let Some(extension) = path.path().extension() {
if extension.to_str().unwrap_or_default() == "meta" {
let metadata: PluginMetadata =
serde_json::from_str(&std::fs::read_to_string(path.path())?)
.map_err(|e| Error::Parse(e.to_string()))?;
database.insert_all(&metadata)?;
}
}
}
Ok(Self {
root: root.as_ref().to_path_buf(),
database: Arc::new(RwLock::new(database)),
signature_verifier: None,
})
}
pub fn with_signature_verifier(mut self, verifier: SignatureVerifier) -> Self {
self.signature_verifier = Some(verifier);
self
}
pub async fn upload(&self, bytes: &[u8], signature: &str) -> Result<UploadResponse> {
if let Some(verifier) = &self.signature_verifier {
if let Err(err) = verifier.is_valid(bytes, signature) {
warn!("invalid file signature for uploaded binary: {}", err);
return Err(Error::Signature("file signature is invalid".to_owned()));
}
}
let descriptors = plugin_analyzer::parse_descriptors(bytes)?;
let digest = sha256::digest(bytes);
let mut file_name = self.root.clone().join(&digest);
file_name.set_extension("plugin");
if file_name.exists() {
warn!("plugin with the same digest was already added");
return Ok(UploadResponse::AlreadyExists);
}
let mut plugin_file = File::create(&file_name).await?;
plugin_file.write_all(bytes).await?;
let metadata = PluginMetadata {
digest: digest.clone(),
signature: signature.to_owned(),
created_at: Utc::now().naive_utc(),
descriptors: descriptors.clone(),
};
file_name.set_extension("meta");
let mut metadata_file = File::create(&file_name).await?;
metadata_file
.write_all(serde_json::to_string(&metadata).unwrap().as_bytes())
.await?;
let mut database = self.database.write();
database.insert_all(&metadata)?;
Ok(UploadResponse::Added)
}
pub async fn download(&self, digest: &str) -> Result<File> {
let mut file_name = self.root.clone().join(digest);
file_name.set_extension("plugin");
Ok(File::open(&file_name).await?)
}
pub async fn metadata(&self, digest: &str) -> Result<PluginMetadata> {
let mut file_name = self.root.clone().join(digest);
file_name.set_extension("meta");
let content = tokio::fs::read_to_string(&file_name).await?;
Ok(serde_json::from_str(&content).unwrap())
}
pub async fn delete(&self, digest: &str) -> Result<()> {
let mut file_name = self.root.clone().join(digest);
file_name.set_extension("plugin");
if !file_name.exists() {
return Err(Error::NotFound("digest was not found".to_owned()));
}
{
let mut database = self.database.write();
database.delete_by_digest(digest);
}
tokio::fs::remove_file(file_name).await?;
Ok(())
}
#[inline]
pub fn health(&self) -> Result<()> {
let paths = std::fs::read_dir(&self.root)?;
for _path in paths.filter_map(|p| p.ok()) {
}
Ok(())
}
#[inline]
pub fn database(&self) -> RwLockReadGuard<RawRwLock, PluginDatabase> {
self.database.read()
}
}