Skip to content
This repository has been archived by the owner on Aug 15, 2024. It is now read-only.

Commit

Permalink
Merge pull request #121 from c4dt/add-dir-check
Browse files Browse the repository at this point in the history
Add dir check
  • Loading branch information
PascalinDe authored Nov 8, 2023
2 parents ef21bcf + 5b0d808 commit 73e62c9
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 15 deletions.
22 changes: 19 additions & 3 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ use tokio_rustls::{
TlsConnector,
};
use tor_config::CfgPath;
use tor_dirmgr::Error;
use tor_rtcompat::tokio::TokioRustlsRuntime as Runtime;
use tracing::{trace, warn};
use tracing::{debug, trace, warn};

use crate::{
flatfiledirmgr::FlatFileDirMgrBuilder,
Expand All @@ -20,6 +21,8 @@ use crate::{

/// Client using the Tor network
pub struct Client(TorClient<Runtime>);
/// AUTHORITY_FILENAME is the name of the file containing the authorities.
pub const AUTHORITY_FILENAME: &str = "authority.json";

impl Client {
/// Create a new client with the given cache directory
Expand All @@ -36,15 +39,28 @@ impl Client {
Ok(Self(tor_client))
}

fn check_directory(cache_path: &Path) -> Result<()> {
if !cache_path.is_dir() {
return Err(Error::CacheCorruption("directory cache does not exist").into());
}
if !cache_path.join(AUTHORITY_FILENAME).exists() {
debug!("required file missing: {}", AUTHORITY_FILENAME);
return Err(Error::CacheCorruption("required file(s) missing in cache").into());
}
Ok(())
}

fn tor_config(cache_path: &Path) -> Result<TorClientConfig> {
let mut cfg_builder = TorClientConfig::builder();
Self::check_directory(cache_path)?;
cfg_builder
.storage()
.cache_dir(CfgPath::new_literal(cache_path))
.state_dir(CfgPath::new_literal(cache_path));

let auth_path = cache_path.join("authority.json");
let auth_raw = fs::read_to_string(auth_path).context("Failed to read authority")?;
let auth_path = cache_path.join(AUTHORITY_FILENAME);
let auth_raw = fs::read_to_string(auth_path.clone())
.context(format!("Failed to read {}", auth_path.to_string_lossy()))?;
let auth = serde_json::from_str(auth_raw.as_str())?;

cfg_builder.tor_network().set_authorities(vec![auth]);
Expand Down
55 changes: 43 additions & 12 deletions src/flatfiledirmgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ use tor_netdir::params::NetParameters;
/// 1/CHURN_FRACTION is the threshold of the consensus relays that we can remove with the churn
const CHURN_FRACTION: usize = 6;

/// Contents of the directory cache.
/// CONSENSUS_FILENAME is the name of the file containing the consensus.
pub const CONSENSUS_FILENAME: &str = "consensus.txt";
/// MICRODESCRIPTORS_FILENAME is the name of the file containing the microdescriptors.
pub const MICRODESCRIPTORS_FILENAME: &str = "microdescriptors.txt";
/// CERTIFICATE_FILENAME is the name of the certificate.
pub const CERTIFICATE_FILENAME: &str = "certificate.txt";
/// CHURN_FILENAME is the name of the churn info file.
pub const CHURN_FILENAME: &str = "churn.txt";

/// A directory manager that loads the directory information from flat files read from the cache
/// directory.
pub struct FlatFileDirMgr<R: Runtime> {
Expand Down Expand Up @@ -70,13 +80,32 @@ impl<R: Runtime> FlatFileDirMgr<R> {
}))
}

/// Check cache directory content.
fn check_directory(cache_path: &Path) -> Result<()> {
let missing_files: Vec<&&str> = [
CONSENSUS_FILENAME,
MICRODESCRIPTORS_FILENAME,
CERTIFICATE_FILENAME,
CHURN_FILENAME,
]
.iter()
.filter(|filename| !cache_path.join(filename).exists())
.collect();
if !missing_files.is_empty() {
debug!("required file(s) missing: {missing_files:?}");
return Err(Error::CacheCorruption("required file(s) missing in cache"));
}
Ok(())
}

/// Try to load the directory from flat files.
///
/// This is strongly inspired by the add_from_cache() methods from the various states in
/// DirMgr, combined and simplified to directly use the data from the loaded files.
pub async fn load_directory(&self) -> Result<bool> {
let config = self.config.get();
let cache_path = &config.cache_path;
Self::check_directory(cache_path)?;

// Consensus
let unvalidated = self.load_consensus(cache_path)?;
Expand Down Expand Up @@ -147,14 +176,14 @@ impl<R: Runtime> FlatFileDirMgr<R> {
&self,
cache_path: &Path,
) -> Result<UnvalidatedConsensus<MdConsensusRouterStatus>> {
let path = cache_path.join("consensus.txt");
let path = cache_path.join(CONSENSUS_FILENAME);
let consensus_text =
fs::read_to_string(path).map_err(|_| Error::UnrecognizedAuthorities)?;
debug!("consensus.txt loaded");
fs::read_to_string(path.clone()).map_err(|_| Error::UnrecognizedAuthorities)?;
debug!("{} loaded", path.to_string_lossy());

let path = cache_path.join("churn.txt");
let churn_text = fs::read_to_string(path).unwrap_or_else(|_| "".to_string());
debug!("churn.txt loaded");
let path = cache_path.join(CHURN_FILENAME);
let churn_text = fs::read_to_string(path.clone()).unwrap_or_else(|_| "".to_string());
debug!("{} loaded", path.to_string_lossy());

let (_, _, parsed) = MdConsensus::parse(&consensus_text)
.map_err(|_| Error::CacheCorruption("Failed to parse consensus"))?;
Expand Down Expand Up @@ -195,9 +224,10 @@ impl<R: Runtime> FlatFileDirMgr<R> {

/// Load the certificate from a flat file.
fn load_certificate(&self, cache_path: &Path) -> Result<AuthCert> {
let path = cache_path.join("certificate.txt");
let certificate = fs::read_to_string(path).map_err(|_| Error::UnrecognizedAuthorities)?;
debug!("certificate.txt loaded");
let path = cache_path.join(CERTIFICATE_FILENAME);
let certificate =
fs::read_to_string(path.clone()).map_err(|_| Error::UnrecognizedAuthorities)?;
debug!("{} loaded", path.to_string_lossy());

let parsed = AuthCert::parse(certificate.as_str())
.map_err(|_| Error::CacheCorruption("Failed to parse certificate"))?
Expand All @@ -211,9 +241,10 @@ impl<R: Runtime> FlatFileDirMgr<R> {

/// Load the list of microdescriptors from a flat file.
fn load_microdesc(&self, cache_path: &Path) -> Result<Vec<Microdesc>> {
let path = cache_path.join("microdescriptors.txt");
let udesc_text = fs::read_to_string(path).map_err(|_| Error::UnrecognizedAuthorities)?;
debug!("microdescriptors.txt loaded");
let path = cache_path.join(MICRODESCRIPTORS_FILENAME);
let udesc_text =
fs::read_to_string(path.clone()).map_err(|_| Error::UnrecognizedAuthorities)?;
debug!("{} loaded", path.to_string_lossy());

let udesc = MicrodescReader::new(
udesc_text.as_str(),
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ mod flatfiledirmgr;
mod http;

pub use client::Client;
pub use client::AUTHORITY_FILENAME;
pub use flatfiledirmgr::CERTIFICATE_FILENAME;
pub use flatfiledirmgr::CHURN_FILENAME;
pub use flatfiledirmgr::CONSENSUS_FILENAME;
pub use flatfiledirmgr::MICRODESCRIPTORS_FILENAME;
53 changes: 53 additions & 0 deletions tests/client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use http::request::{Builder, Parts};
use http::Request;
use lightarti_rest::Client;
use lightarti_rest::AUTHORITY_FILENAME;
use lightarti_rest::CERTIFICATE_FILENAME;
use lightarti_rest::CHURN_FILENAME;
use lightarti_rest::CONSENSUS_FILENAME;
use lightarti_rest::MICRODESCRIPTORS_FILENAME;
use url::Url;

mod utils;
Expand Down Expand Up @@ -85,6 +90,54 @@ async fn test_client(req: Request<Vec<u8>>) {
)
}

#[tokio::test]
// Tests that no error is raised if directory is left intact.
// If this tests raises an error, please check if your local copy of directory_cache is up to date.
async fn test_required_files_ok() {
let cache = utils::setup_cache();
let res = Client::new(cache.path()).await;
assert!(res.is_ok());
}

#[tokio::test]
// Tests that an error is raised by FlatFileDirMgr::check_directory if any of the required files
// are missing. The authority.json file is checked for in Client::check_directory since it is used
// before the other files are read in.
async fn test_required_files_missing() {
for filename in [
CONSENSUS_FILENAME,
MICRODESCRIPTORS_FILENAME,
CERTIFICATE_FILENAME,
CHURN_FILENAME,
AUTHORITY_FILENAME,
]
.iter()
{
let cache = utils::setup_cache();
let _ = std::fs::remove_file(cache.path().join(filename));
let res = Client::new(cache.path()).await;
let error = res.err().expect("");
let root_cause = error.root_cause();
assert_eq!(
format!("{}", root_cause),
"Corrupt cache: required file(s) missing in cache"
);
}
}

#[tokio::test]
async fn test_directory_not_existing() {
let cache = utils::setup_cache();
let _ = std::fs::remove_dir_all(cache.path());
let res = Client::new(cache.path()).await;
let error = res.err().expect("");
let root_cause = error.root_cause();
assert_eq!(
format!("{}", root_cause),
"Corrupt cache: directory cache does not exist"
);
}

fn clone_request(header: &Parts, body: &[u8]) -> Request<Vec<u8>> {
let mut builder = Builder::new()
.method(header.method.clone())
Expand Down

0 comments on commit 73e62c9

Please sign in to comment.