use tor_netdoc::doc::authcert::AuthCertKeyIds;
use tor_netdoc::doc::microdesc::MdDigest;
use tor_netdoc::doc::netstatus::{ConsensusFlavor, ProtoStatuses};
#[cfg(feature = "routerdesc")]
use tor_netdoc::doc::routerdesc::RdDigest;
#[cfg(feature = "bridge-client")]
pub(crate) use tor_guardmgr::bridge::BridgeConfig;
use crate::docmeta::{AuthCertMeta, ConsensusMeta};
use crate::{Error, Result};
use std::cell::RefCell;
use std::collections::HashMap;
use std::fs::File;
use std::io::Result as IoResult;
use std::str::Utf8Error;
use std::time::SystemTime;
use time::Duration;
pub(crate) mod sqlite;
pub(crate) use sqlite::SqliteStore;
pub(crate) type DynStore = Box<dyn Store>;
pub struct DocumentText {
s: InputString,
}
impl From<InputString> for DocumentText {
fn from(s: InputString) -> DocumentText {
DocumentText { s }
}
}
impl AsRef<[u8]> for DocumentText {
fn as_ref(&self) -> &[u8] {
self.s.as_ref()
}
}
impl DocumentText {
pub(crate) fn as_str(&self) -> std::result::Result<&str, Utf8Error> {
self.s.as_str_impl()
}
pub(crate) fn from_string(s: String) -> Self {
DocumentText {
s: InputString::Utf8(s),
}
}
}
#[derive(Debug)]
pub(crate) enum InputString {
Utf8(String),
UncheckedBytes {
bytes: Vec<u8>,
validated: RefCell<bool>,
},
#[cfg(feature = "mmap")]
MappedBytes {
bytes: memmap2::Mmap,
validated: RefCell<bool>,
},
}
impl InputString {
pub(crate) fn as_str(&self) -> Result<&str> {
self.as_str_impl().map_err(Error::BadUtf8InCache)
}
fn as_str_impl(&self) -> std::result::Result<&str, Utf8Error> {
match self {
InputString::Utf8(s) => Ok(&s[..]),
InputString::UncheckedBytes { bytes, validated } => {
if *validated.borrow() {
unsafe { Ok(std::str::from_utf8_unchecked(&bytes[..])) }
} else {
let result = std::str::from_utf8(&bytes[..])?;
validated.replace(true);
Ok(result)
}
}
#[cfg(feature = "mmap")]
InputString::MappedBytes { bytes, validated } => {
if *validated.borrow() {
unsafe { Ok(std::str::from_utf8_unchecked(&bytes[..])) }
} else {
let result = std::str::from_utf8(&bytes[..])?;
validated.replace(true);
Ok(result)
}
}
}
}
pub(crate) fn load(file: File) -> IoResult<Self> {
#[cfg(feature = "mmap")]
{
let mapping = unsafe {
memmap2::Mmap::map(&file)
};
if let Ok(bytes) = mapping {
return Ok(InputString::MappedBytes {
bytes,
validated: RefCell::new(false),
});
}
}
use std::io::{BufReader, Read};
let mut f = BufReader::new(file);
let mut result = String::new();
f.read_to_string(&mut result)?;
Ok(InputString::Utf8(result))
}
}
impl AsRef<[u8]> for InputString {
fn as_ref(&self) -> &[u8] {
match self {
InputString::Utf8(s) => s.as_ref(),
InputString::UncheckedBytes { bytes, .. } => &bytes[..],
#[cfg(feature = "mmap")]
InputString::MappedBytes { bytes, .. } => &bytes[..],
}
}
}
impl From<String> for InputString {
fn from(s: String) -> InputString {
InputString::Utf8(s)
}
}
impl From<Vec<u8>> for InputString {
fn from(bytes: Vec<u8>) -> InputString {
InputString::UncheckedBytes {
bytes,
validated: RefCell::new(false),
}
}
}
pub(crate) struct ExpirationConfig {
pub(super) router_descs: Duration,
pub(super) microdescs: Duration,
pub(super) authcerts: Duration,
pub(super) consensuses: Duration,
}
pub(crate) const EXPIRATION_DEFAULTS: ExpirationConfig = {
ExpirationConfig {
router_descs: Duration::days(5),
microdescs: Duration::days(7),
authcerts: Duration::ZERO,
consensuses: Duration::days(2),
}
};
pub(crate) trait Store: Send + 'static {
fn is_readonly(&self) -> bool;
fn upgrade_to_readwrite(&mut self) -> Result<bool>;
fn expire_all(&mut self, expiration: &ExpirationConfig) -> Result<()>;
fn latest_consensus(
&self,
flavor: ConsensusFlavor,
pending: Option<bool>,
) -> Result<Option<InputString>>;
fn latest_consensus_meta(&self, flavor: ConsensusFlavor) -> Result<Option<ConsensusMeta>>;
#[cfg(test)]
fn consensus_by_meta(&self, cmeta: &ConsensusMeta) -> Result<InputString>;
fn consensus_by_sha3_digest_of_signed_part(
&self,
d: &[u8; 32],
) -> Result<Option<(InputString, ConsensusMeta)>>;
fn store_consensus(
&mut self,
cmeta: &ConsensusMeta,
flavor: ConsensusFlavor,
pending: bool,
contents: &str,
) -> Result<()>;
fn mark_consensus_usable(&mut self, cmeta: &ConsensusMeta) -> Result<()>;
#[allow(dead_code)] fn delete_consensus(&mut self, cmeta: &ConsensusMeta) -> Result<()>;
fn authcerts(&self, certs: &[AuthCertKeyIds]) -> Result<HashMap<AuthCertKeyIds, String>>;
fn store_authcerts(&mut self, certs: &[(AuthCertMeta, &str)]) -> Result<()>;
fn microdescs(&self, digests: &[MdDigest]) -> Result<HashMap<MdDigest, String>>;
fn store_microdescs(&mut self, digests: &[(&str, &MdDigest)], when: SystemTime) -> Result<()>;
fn update_microdescs_listed(&mut self, digests: &[MdDigest], when: SystemTime) -> Result<()>;
#[cfg(feature = "routerdesc")]
fn routerdescs(&self, digests: &[RdDigest]) -> Result<HashMap<RdDigest, String>>;
#[cfg(feature = "routerdesc")]
#[allow(unused)]
fn store_routerdescs(&mut self, digests: &[(&str, SystemTime, &RdDigest)]) -> Result<()>;
#[cfg(feature = "bridge-client")]
fn lookup_bridgedesc(&self, bridge: &BridgeConfig) -> Result<Option<CachedBridgeDescriptor>>;
#[cfg(feature = "bridge-client")]
fn store_bridgedesc(
&mut self,
bridge: &BridgeConfig,
entry: CachedBridgeDescriptor,
until: SystemTime,
) -> Result<()>;
#[cfg(feature = "bridge-client")]
#[allow(dead_code)] fn delete_bridgedesc(&mut self, bridge: &BridgeConfig) -> Result<()>;
fn update_protocol_recommendations(
&mut self,
valid_after: SystemTime,
protocols: &ProtoStatuses,
) -> Result<()>;
fn cached_protocol_recommendations(&self) -> Result<Option<(SystemTime, ProtoStatuses)>>;
}
#[derive(Clone, Debug)]
#[cfg_attr(not(feature = "bridge-client"), allow(dead_code))]
pub(crate) struct CachedBridgeDescriptor {
pub(crate) fetched: SystemTime,
pub(crate) document: String,
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use tempfile::tempdir;
#[test]
fn strings() {
let s: InputString = "Hello world".to_string().into();
assert_eq!(s.as_ref(), b"Hello world");
assert_eq!(s.as_str().unwrap(), "Hello world");
assert_eq!(s.as_str().unwrap(), "Hello world");
let s: InputString = b"Hello world".to_vec().into();
assert_eq!(s.as_ref(), b"Hello world");
assert_eq!(s.as_str().unwrap(), "Hello world");
assert_eq!(s.as_str().unwrap(), "Hello world");
let s: InputString = b"Hello \xff world".to_vec().into();
assert_eq!(s.as_ref(), b"Hello \xff world");
assert!(s.as_str().is_err());
}
#[test]
fn files() {
let td = tempdir().unwrap();
let goodstr = td.path().join("goodstr");
std::fs::write(&goodstr, "This is a reasonable file.\n").unwrap();
let s = InputString::load(File::open(goodstr).unwrap());
let s = s.unwrap();
assert_eq!(s.as_str().unwrap(), "This is a reasonable file.\n");
assert_eq!(s.as_str().unwrap(), "This is a reasonable file.\n");
assert_eq!(s.as_ref(), b"This is a reasonable file.\n");
let badutf8 = td.path().join("badutf8");
std::fs::write(&badutf8, b"Not good \xff UTF-8.\n").unwrap();
let s = InputString::load(File::open(badutf8).unwrap());
assert!(s.is_err() || s.unwrap().as_str().is_err());
}
#[test]
fn doctext() {
let s: InputString = "Hello universe".to_string().into();
let dt: DocumentText = s.into();
assert_eq!(dt.as_ref(), b"Hello universe");
assert_eq!(dt.as_str(), Ok("Hello universe"));
assert_eq!(dt.as_str(), Ok("Hello universe"));
let s: InputString = b"Hello \xff universe".to_vec().into();
let dt: DocumentText = s.into();
assert_eq!(dt.as_ref(), b"Hello \xff universe");
assert!(dt.as_str().is_err());
}
}