1use tor_netdoc::doc::authcert::AuthCertKeyIds;
10use tor_netdoc::doc::microdesc::MdDigest;
11use tor_netdoc::doc::netstatus::{ConsensusFlavor, ProtoStatuses};
12
13#[cfg(feature = "routerdesc")]
14use tor_netdoc::doc::routerdesc::RdDigest;
15
16#[cfg(feature = "bridge-client")]
17pub(crate) use tor_guardmgr::bridge::BridgeConfig;
18
19use crate::docmeta::{AuthCertMeta, ConsensusMeta};
20use crate::{Error, Result};
21use std::cell::RefCell;
22use std::collections::HashMap;
23use std::fs::File;
24use std::io::Result as IoResult;
25use std::str::Utf8Error;
26use std::time::SystemTime;
27use time::Duration;
28
29pub(crate) mod sqlite;
30
31pub(crate) use sqlite::SqliteStore;
32
33pub(crate) type DynStore = Box<dyn Store>;
35
36pub struct DocumentText {
41 s: InputString,
45}
46
47impl From<InputString> for DocumentText {
48 fn from(s: InputString) -> DocumentText {
49 DocumentText { s }
50 }
51}
52
53impl AsRef<[u8]> for DocumentText {
54 fn as_ref(&self) -> &[u8] {
55 self.s.as_ref()
56 }
57}
58
59impl DocumentText {
60 pub(crate) fn as_str(&self) -> std::result::Result<&str, Utf8Error> {
62 self.s.as_str_impl()
63 }
64
65 pub(crate) fn from_string(s: String) -> Self {
67 DocumentText {
68 s: InputString::Utf8(s),
69 }
70 }
71}
72
73#[derive(Debug)]
76pub(crate) enum InputString {
77 Utf8(String),
79 UncheckedBytes {
81 bytes: Vec<u8>,
83 validated: RefCell<bool>,
85 },
86 #[cfg(feature = "mmap")]
87 MappedBytes {
89 bytes: memmap2::Mmap,
91 validated: RefCell<bool>,
93 },
94}
95
96impl InputString {
97 pub(crate) fn as_str(&self) -> Result<&str> {
99 self.as_str_impl().map_err(Error::BadUtf8InCache)
100 }
101
102 fn as_str_impl(&self) -> std::result::Result<&str, Utf8Error> {
104 match self {
109 InputString::Utf8(s) => Ok(&s[..]),
110 InputString::UncheckedBytes { bytes, validated } => {
111 if *validated.borrow() {
112 unsafe { Ok(std::str::from_utf8_unchecked(&bytes[..])) }
113 } else {
114 let result = std::str::from_utf8(&bytes[..])?;
115 validated.replace(true);
116 Ok(result)
117 }
118 }
119 #[cfg(feature = "mmap")]
120 InputString::MappedBytes { bytes, validated } => {
121 if *validated.borrow() {
122 unsafe { Ok(std::str::from_utf8_unchecked(&bytes[..])) }
123 } else {
124 let result = std::str::from_utf8(&bytes[..])?;
125 validated.replace(true);
126 Ok(result)
127 }
128 }
129 }
130 }
131 pub(crate) fn load(file: File) -> IoResult<Self> {
137 #[cfg(feature = "mmap")]
138 {
139 let mapping = unsafe {
140 memmap2::Mmap::map(&file)
144 };
145 if let Ok(bytes) = mapping {
146 return Ok(InputString::MappedBytes {
147 bytes,
148 validated: RefCell::new(false),
149 });
150 }
151 }
152 use std::io::{BufReader, Read};
153 let mut f = BufReader::new(file);
154 let mut result = String::new();
155 f.read_to_string(&mut result)?;
156 Ok(InputString::Utf8(result))
157 }
158}
159
160impl AsRef<[u8]> for InputString {
161 fn as_ref(&self) -> &[u8] {
162 match self {
163 InputString::Utf8(s) => s.as_ref(),
164 InputString::UncheckedBytes { bytes, .. } => &bytes[..],
165 #[cfg(feature = "mmap")]
166 InputString::MappedBytes { bytes, .. } => &bytes[..],
167 }
168 }
169}
170
171impl From<String> for InputString {
172 fn from(s: String) -> InputString {
173 InputString::Utf8(s)
174 }
175}
176
177impl From<Vec<u8>> for InputString {
178 fn from(bytes: Vec<u8>) -> InputString {
179 InputString::UncheckedBytes {
180 bytes,
181 validated: RefCell::new(false),
182 }
183 }
184}
185
186pub(crate) struct ExpirationConfig {
188 pub(super) router_descs: Duration,
196 pub(super) microdescs: Duration,
204 pub(super) authcerts: Duration,
206 pub(super) consensuses: Duration,
208}
209
210pub(crate) const EXPIRATION_DEFAULTS: ExpirationConfig = {
212 ExpirationConfig {
213 router_descs: Duration::days(5),
221 microdescs: Duration::days(7),
228 authcerts: Duration::ZERO,
229 consensuses: Duration::days(2),
230 }
231};
232
233pub(crate) trait Store: Send + 'static {
238 fn is_readonly(&self) -> bool;
240 fn upgrade_to_readwrite(&mut self) -> Result<bool>;
244
245 fn expire_all(&mut self, expiration: &ExpirationConfig) -> Result<()>;
250
251 fn latest_consensus(
258 &self,
259 flavor: ConsensusFlavor,
260 pending: Option<bool>,
261 ) -> Result<Option<InputString>>;
262 fn latest_consensus_meta(&self, flavor: ConsensusFlavor) -> Result<Option<ConsensusMeta>>;
265 #[cfg(test)]
267 fn consensus_by_meta(&self, cmeta: &ConsensusMeta) -> Result<InputString>;
268 fn consensus_by_sha3_digest_of_signed_part(
271 &self,
272 d: &[u8; 32],
273 ) -> Result<Option<(InputString, ConsensusMeta)>>;
274 fn store_consensus(
276 &mut self,
277 cmeta: &ConsensusMeta,
278 flavor: ConsensusFlavor,
279 pending: bool,
280 contents: &str,
281 ) -> Result<()>;
282 fn mark_consensus_usable(&mut self, cmeta: &ConsensusMeta) -> Result<()>;
284 #[allow(dead_code)] fn delete_consensus(&mut self, cmeta: &ConsensusMeta) -> Result<()>;
289
290 fn authcerts(&self, certs: &[AuthCertKeyIds]) -> Result<HashMap<AuthCertKeyIds, String>>;
292 fn store_authcerts(&mut self, certs: &[(AuthCertMeta, &str)]) -> Result<()>;
294
295 fn microdescs(&self, digests: &[MdDigest]) -> Result<HashMap<MdDigest, String>>;
297 fn store_microdescs(&mut self, digests: &[(&str, &MdDigest)], when: SystemTime) -> Result<()>;
300 fn update_microdescs_listed(&mut self, digests: &[MdDigest], when: SystemTime) -> Result<()>;
303
304 #[cfg(feature = "routerdesc")]
308 fn routerdescs(&self, digests: &[RdDigest]) -> Result<HashMap<RdDigest, String>>;
309 #[cfg(feature = "routerdesc")]
311 #[allow(unused)]
312 fn store_routerdescs(&mut self, digests: &[(&str, SystemTime, &RdDigest)]) -> Result<()>;
313
314 #[cfg(feature = "bridge-client")]
316 fn lookup_bridgedesc(&self, bridge: &BridgeConfig) -> Result<Option<CachedBridgeDescriptor>>;
317
318 #[cfg(feature = "bridge-client")]
324 fn store_bridgedesc(
325 &mut self,
326 bridge: &BridgeConfig,
327 entry: CachedBridgeDescriptor,
328 until: SystemTime,
329 ) -> Result<()>;
330
331 #[cfg(feature = "bridge-client")]
335 #[allow(dead_code)] fn delete_bridgedesc(&mut self, bridge: &BridgeConfig) -> Result<()>;
338
339 fn update_protocol_recommendations(
341 &mut self,
342 valid_after: SystemTime,
343 protocols: &ProtoStatuses,
344 ) -> Result<()>;
345
346 fn cached_protocol_recommendations(&self) -> Result<Option<(SystemTime, ProtoStatuses)>>;
348}
349
350#[derive(Clone, Debug)]
352#[cfg_attr(not(feature = "bridge-client"), allow(dead_code))]
353pub(crate) struct CachedBridgeDescriptor {
354 pub(crate) fetched: SystemTime,
356
357 pub(crate) document: String,
359}
360
361#[cfg(test)]
362mod test {
363 #![allow(clippy::bool_assert_comparison)]
365 #![allow(clippy::clone_on_copy)]
366 #![allow(clippy::dbg_macro)]
367 #![allow(clippy::mixed_attributes_style)]
368 #![allow(clippy::print_stderr)]
369 #![allow(clippy::print_stdout)]
370 #![allow(clippy::single_char_pattern)]
371 #![allow(clippy::unwrap_used)]
372 #![allow(clippy::unchecked_duration_subtraction)]
373 #![allow(clippy::useless_vec)]
374 #![allow(clippy::needless_pass_by_value)]
375 use super::*;
377 use tempfile::tempdir;
378
379 #[test]
380 fn strings() {
381 let s: InputString = "Hello world".to_string().into();
382 assert_eq!(s.as_ref(), b"Hello world");
383 assert_eq!(s.as_str().unwrap(), "Hello world");
384 assert_eq!(s.as_str().unwrap(), "Hello world");
385
386 let s: InputString = b"Hello world".to_vec().into();
387 assert_eq!(s.as_ref(), b"Hello world");
388 assert_eq!(s.as_str().unwrap(), "Hello world");
389 assert_eq!(s.as_str().unwrap(), "Hello world");
390
391 let s: InputString = b"Hello \xff world".to_vec().into();
393 assert_eq!(s.as_ref(), b"Hello \xff world");
394 assert!(s.as_str().is_err());
395 }
396
397 #[test]
398 fn files() {
399 let td = tempdir().unwrap();
400
401 let goodstr = td.path().join("goodstr");
402 std::fs::write(&goodstr, "This is a reasonable file.\n").unwrap();
403 let s = InputString::load(File::open(goodstr).unwrap());
404 let s = s.unwrap();
405 assert_eq!(s.as_str().unwrap(), "This is a reasonable file.\n");
406 assert_eq!(s.as_str().unwrap(), "This is a reasonable file.\n");
407 assert_eq!(s.as_ref(), b"This is a reasonable file.\n");
408
409 let badutf8 = td.path().join("badutf8");
410 std::fs::write(&badutf8, b"Not good \xff UTF-8.\n").unwrap();
411 let s = InputString::load(File::open(badutf8).unwrap());
412 assert!(s.is_err() || s.unwrap().as_str().is_err());
413 }
414
415 #[test]
416 fn doctext() {
417 let s: InputString = "Hello universe".to_string().into();
418 let dt: DocumentText = s.into();
419 assert_eq!(dt.as_ref(), b"Hello universe");
420 assert_eq!(dt.as_str(), Ok("Hello universe"));
421 assert_eq!(dt.as_str(), Ok("Hello universe"));
422
423 let s: InputString = b"Hello \xff universe".to_vec().into();
424 let dt: DocumentText = s.into();
425 assert_eq!(dt.as_ref(), b"Hello \xff universe");
426 assert!(dt.as_str().is_err());
427 }
428}