Merge branch 'hss_apis' into 'main'

hsservice: Initial data structures and APIs

Closes #972, #971, and #970

See merge request tpo/core/arti!1452
This commit is contained in:
Nick Mathewson 2023-08-01 17:06:10 +00:00
commit dee95436f5
10 changed files with 558 additions and 89 deletions

3
Cargo.lock generated
View File

@ -4913,10 +4913,13 @@ name = "tor-hsservice"
version = "0.2.3" version = "0.2.3"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"futures",
"rand_core 0.6.4", "rand_core 0.6.4",
"thiserror", "thiserror",
"tor-circmgr", "tor-circmgr",
"tor-hscrypto", "tor-hscrypto",
"tor-keymgr",
"tor-linkspec",
"tor-llcrypto", "tor-llcrypto",
"tor-netdir", "tor-netdir",
"tor-proto", "tor-proto",

View File

@ -26,10 +26,13 @@ full = [
[dependencies] [dependencies]
async-trait = "0.1.54" async-trait = "0.1.54"
futures = "0.3.14"
rand_core = "0.6.2" rand_core = "0.6.2"
thiserror = "1" thiserror = "1"
tor-circmgr = { version = "0.10.0", path = "../tor-circmgr", features = ["hs-service"] } tor-circmgr = { version = "0.10.0", path = "../tor-circmgr", features = ["hs-service"] }
tor-hscrypto = { version = "0.3.0", path = "../tor-hscrypto" } tor-hscrypto = { version = "0.3.0", path = "../tor-hscrypto" }
tor-keymgr = { version = "0.1.0", path = "../tor-keymgr" }
tor-linkspec = { version = "0.8.1", path = "../tor-linkspec" }
tor-llcrypto = { version = "0.5.2", path = "../tor-llcrypto" } tor-llcrypto = { version = "0.5.2", path = "../tor-llcrypto" }
tor-netdir = { version = "0.9.3", path = "../tor-netdir" } tor-netdir = { version = "0.9.3", path = "../tor-netdir" }
tor-proto = { version = "0.12.0", path = "../tor-proto" } tor-proto = { version = "0.12.0", path = "../tor-proto" }

View File

@ -0,0 +1,25 @@
//! Configuration information for onion services.
/// Configuration for a single onion service.
#[derive(Debug, Clone)]
pub struct OnionServiceConfig {
/// An arbitrary identifier or "nickname" used to look up this service's
/// keys, state, configuration, etc,
/// and distinguish them from other services. This is local-only.
//
// TODO HSS: It's possible that instead of having this be _part_ of the
// service's configuration, we want this to be the key for a map in
// which the service's configuration is stored. We'll see how the code
// evolves.
nickname: String,
/// Whether we want this to be a non-anonymous "single onion service".
/// We could skip this in v1. We should make sure that our state
/// is built to make it hard to accidentally set this.
anonymity: crate::Anonymity,
/// Number of intro points; defaults to 3; max 20.
num_intro_points: Option<u8>,
// TODO HSS: I'm not sure if client encryption belongs as a configuration
// item, or as a directory like C tor does it. Or both?
}

View File

@ -1,46 +1,74 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
// TODO hss: Add complete suite of warnings here. // @@ begin lint list maintained by maint/add_warning @@
#![cfg_attr(not(ci_arti_stable), allow(renamed_and_removed_lints))]
#![cfg_attr(not(ci_arti_nightly), allow(unknown_lints))]
#![deny(missing_docs)]
#![warn(noop_method_call)]
#![deny(unreachable_pub)]
#![warn(clippy::all)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
#![deny(clippy::exhaustive_structs)]
#![deny(clippy::expl_impl_clone_on_copy)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::implicit_clone)]
#![deny(clippy::large_stack_arrays)]
#![warn(clippy::manual_ok_or)]
#![deny(clippy::missing_docs_in_private_items)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::option_option)]
#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]
#![warn(clippy::rc_buffer)]
#![deny(clippy::ref_option_ref)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::trait_duplication_in_bounds)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
#![allow(dead_code, unused_variables)] // TODO hss remove. #![allow(dead_code, unused_variables)] // TODO hss remove.
mod config;
mod err; mod err;
mod keys; mod keys;
mod req;
mod status; mod status;
mod streamproxy;
mod svc; mod svc;
use async_trait::async_trait; pub use config::OnionServiceConfig;
pub use err::Error; pub use err::Error;
pub use req::{OnionServiceDataStream, RendRequest, StreamRequest};
pub use status::OnionServiceStatus; pub use status::OnionServiceStatus;
pub use svc::OnionService; pub use svc::OnionService;
/// A Result type describing an onion service operation. /// A Result type describing an onion service operation.
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
/// An object that knows how to handle stream requests. /// The level of anonymity that an onion service should try to run with.
#[async_trait] #[derive(Debug, Default, Copy, Clone)]
pub trait StreamHandler { #[non_exhaustive]
/// Handle an incoming stream request on a given onion service. pub enum Anonymity {
/// Try to keep the location of the onion service private.
#[default]
Anonymous,
/// Do not try to keep the location of the onion service private.
///
/// (This is implemented using our "single onion service" design.)
// //
// TODO hss: the `circ_info` argument should have data about the circuit on // TODO HSS: We may want to put this behind a feature?
// which the request arrived. If the client authenticated, it might tell us DangerouslyNonAnonymous,
// who they are. Or it might have information about how many requests
// (and/or failed requests) we've gotten on the circuit.
//
// TODO hss: The `circ_info` argument should at a minimum include the
// circuit; ideally in a form that we can get a weak reference to it, and
// use it in the key of a `PtrWeakKeyHashMap`. (Or we could stick the info
// in the circuit itself somehow, and access it as a Box<dyn Any>, but
// that's a bit sketchy type-wise.)
//
// TODO hss: the `stream` argument should be an IncomingStream from
// tor-proto, but that branch is not yet merged as of this writing.
async fn handle_request(&self, circ_info: &(), stream: ());
}
mod mgr {
// TODO hss: Do we want to have the notion of a collection of onion services,
// running in tandem? Or is that a higher-level crate, possibly a part of
// TorClient?
} }

View File

@ -0,0 +1,141 @@
//! Request objects used to implement onion services.
//!
//! These requests are yielded on a stream, and the calling code needs to decide
//! whether to permit or reject them.
use futures::{channel::mpsc, stream::Stream};
use std::net::SocketAddr;
use tor_linkspec::OwnedChanTarget;
use tor_llcrypto::pk::curve25519;
use tor_proto::stream::DataStream;
use crate::Result;
/// Request to complete an introduction/rendezvous handshake.
///
/// A request of this kind indicates that a client has asked permission to
/// connect to an onion service through an introduction point. The caller needs
/// to decide whether or not to complete the handshake.
///
/// Protocol details: More specifically, we create one of these whenever we get a well-formed
/// `INTRODUCE2` message. Based on this, the caller decides whether to send a
/// `RENDEZVOUS1` message.
#[derive(Debug)]
pub struct RendRequest {
/// Which introduction point gave us this request?
from_intro_point: crate::svc::IntroPointId,
/// What proof-of-work did the client send us?
proof_of_work_provided: Option<ProofOfWork>,
/// Information about the rendezvous point that the client wanted us to
/// connect to.
rend_pt: RendPt,
//
// TODO HSS: We'll also need additional information to actually complete the
// request, maybe including a Weak<OnionService>, or maybe including a
// oneshot::Sender.
}
/// Information needed to complete a rendezvous handshake.
#[derive(Debug, Clone)]
struct RendPt {
/// The location and identity of the rendezvous point.
location: OwnedChanTarget,
/// The public Ntor key for the rendezvous point.
ntor_key: curve25519::PublicKey,
/// Cryptographic state to use when completing the handshake.
///
/// TODO HSS: This is not at all final, and should be refactored freely.
handshake: HandshakeState,
}
/// The cryptographic state needed to complete an introduce/rendezvous
/// handshake.
#[derive(Debug, Clone)]
struct HandshakeState {
// TODO HSS: replace this type or its contents as needed.
}
/// Information about a proof of work received from a client's introduction
/// point.
///
// Todo: use Beth's API instead.
#[derive(Debug, Clone)]
enum ProofOfWork {
/// TODO HSS document or replace.
EquixV1 {
/// TODO HSS document or replace
effort_level: usize,
},
}
/// A request from a client to open a new stream to an onion service.
///
/// We can only receive these _after_ we have already permitted the client to
/// connect via a [`RendRequest`].
///
/// Protocol details: More specifically, we create one of these whenever we get a well-formed
/// `BEGIN` message. Based on this, the caller decides whether to send a
/// `CONNECTED` message.
#[derive(Debug)]
pub struct StreamRequest {
/// The object that will be used to send data to and from the client.
///
/// TODO HSS: Possibly instead this will be some type from tor_proto that
/// can turn into a DataStream.
stream: DataStream,
/// The address that the client has asked to connect to.
///
/// TODO HSS: This is the wrong type! It may be a hostname.
target: SocketAddr,
}
/// A stream opened over an onion service.
//
// TODO HSS: This may belong in another module.
#[derive(Debug)]
pub struct OnionServiceDataStream {
/// The underlying data stream; this type is just a thin wrapper.
inner: DataStream,
}
impl RendRequest {
/// Mark this request as accepted, and try to connect to the client's
/// provided rendezvous point.
///
/// TODO HSS: Should this really be async? It might be nicer if it weren't.
pub async fn accept(self) -> Result<impl Stream<Item = StreamRequest>> {
let r: Result<mpsc::Receiver<StreamRequest>>;
todo!();
#[allow(unreachable_code)]
r
}
/// Reject this request. (The client will receive no notification.)
///
/// TODO HSS: Should this really be async? It might be nicer if it weren't.
pub async fn reject(self) -> Result<()> {
todo!()
}
//
// TODO HSS: also add various accessors
}
impl StreamRequest {
/// Accept this request and send the client a `CONNECTED` message.
pub async fn accept(self) -> Result<OnionServiceDataStream> {
todo!()
}
/// Reject this request, and send the client an `END` message.
pub async fn reject(self) -> Result<()> {
todo!()
}
/// Reject this request and close the rendezvous circuit entirely,
/// along with all other streams attached to the circuit.
pub fn shutdown_circuit(self) -> Result<()> {
todo!()
}
// TODO HSS various accessors, including for circuit.
}

View File

@ -1,4 +1,10 @@
//! Support for reporting the status of an onion service.
/// The current reported status of an onion service.
#[derive(Debug, Clone)]
pub struct OnionServiceStatus { pub struct OnionServiceStatus {
// TODO hss Should say how many intro points are active, how many descriptors // TODO hss Should say how many intro points are active, how many descriptors
// are updated, whether we're "healthy", etc. // are updated, whether we're "healthy", etc.
/// An ignored field to suppress warnings. TODO HSS remove this.
_ignore: (),
} }

View File

@ -1,36 +0,0 @@
//! Implement a StreamHandler that proxies connections to ports, typically on
//! localhost.
use std::{collections::HashMap, net::SocketAddr};
use async_trait::async_trait;
use crate::StreamHandler;
pub(crate) struct StreamProxy {
/// Map from virtual port on the onion service to an address we connect to
/// in order to implement that port.
ports: HashMap<u16, SocketAddr>,
}
impl StreamProxy {
// TODO hss need a new() function. It should reject non-localhost addresses
// by default, and have a way to override. (Alternatively, that should be
// done in the configuration code?)
}
#[allow(clippy::diverging_sub_expression)] // for todo!() + async_trait.
#[async_trait]
impl StreamHandler for StreamProxy {
async fn handle_request(&self, circinfo: &(), stream: ()) {
todo!() // TODO hss: implement
// - Look up the port for the incoming stream request.
// - If no port is found, reject the request, and possibly increment a
// counter in circinfo.
// - Otherwise, open a TCP connection to the target address.
// - On success, accept the stream, and launch tasks to relay traffic
// from the stream to the TCP connection.
// - On failure, reject the stream with an error.
}
}

View File

@ -1,49 +1,134 @@
use std::sync::Arc; //! Principal types for onion services.
use tor_circmgr::CircMgr; use std::{
collections::HashMap,
sync::{Arc, Mutex},
time::Instant,
};
use tor_circmgr::hspool::HsCircPool;
use tor_hscrypto::pk::HsBlindIdKey;
use tor_keymgr::KeyMgr;
use tor_linkspec::RelayIds;
use tor_llcrypto::pk::curve25519;
use tor_netdir::NetDirProvider; use tor_netdir::NetDirProvider;
use tor_rtcompat::Runtime; use tor_rtcompat::Runtime;
use crate::{OnionServiceStatus, Result}; use crate::{OnionServiceStatus, Result};
mod ipt_establish;
mod publish;
/// A handle to an instance of an onion service. /// A handle to an instance of an onion service.
// //
// TODO hss: We might want to wrap this in an Arc<Mutex<>>, and have an inner // TODO HSS: Write more.
// structure that contains these elements. Or we might want to refactor this in //
// some other way. // (APIs should return Arc<OnionService>)
//
// NOTE: This might not need to be parameterized on Runtime; if we can avoid it
// without too much trouble, we should.
pub struct OnionService<R: Runtime> { pub struct OnionService<R: Runtime> {
/// Needs some kind of configuration about: what is our identity (if we know /// The mutable implementation details of this onion service.
/// it), is this anonymous, do we store persistent info and if so where and inner: Mutex<SvcInner<R>>,
/// how, etc. }
/// Implementation details for an onion service.
struct SvcInner<R: Runtime> {
/// Configuration information about this service.
/// ///
/// Authorized client public keys might be here, or they might be in a /// TODO HSS: Authorized client public keys might be here, or they might be in a
/// separate structure. /// separate structure.
config: (), config: crate::OnionServiceConfig,
/// A netdir provider to use in finding our directories and choosing our /// A netdir provider to use in finding our directories and choosing our
/// introduction points. /// introduction points.
netdir_provider: Arc<dyn NetDirProvider>, netdir_provider: Arc<dyn NetDirProvider>,
/// A circuit manager to use in making circuits to our introduction points,
/// A keymgr used to look up our keys and store new medium-term keys.
keymgr: Arc<KeyMgr>,
/// A circuit pool to use in making circuits to our introduction points,
/// HsDirs, and rendezvous points. /// HsDirs, and rendezvous points.
//
// TODO hss: Maybe we can make a trait that only gives a minimal "build a // TODO hss: Maybe we can make a trait that only gives a minimal "build a
// circuit" API from CircMgr, so that we can have this be a dyn reference // circuit" API from CircMgr, so that we can have this be a dyn reference
// too? // too?
circmgr: Arc<CircMgr<R>>, circmgr: Arc<HsCircPool<R>>,
/// Private keys in actual use for this onion service.
///
/// TODO hss: This will need heavy refactoring.
///
/// TODO hss: There's a separate blinded ID, certificate, and signing key
/// for each active time period.
keys: (),
/// Status for each active introduction point for this onion service.
intro_points: Vec<()>,
/// Status for our onion service descriptor
descriptor_status: (),
/// Object that handles incoming streams from the client. /// Authentication information for descriptor encryption.
stream_handler: Arc<dyn crate::StreamHandler>, ///
/// (Our protocol defines two kinds of client authentication: in the first
/// type, we encrypt the descriptor to client public keys. In the second,
/// we require authentictaion as part of the `INTRODUCE2` message. Only the
/// first type has ever been implemented.)
encryption_auth: Option<DescEncryptionAuth>,
/// Private keys in actual use for this onion service.
//
// TODO hss: This will need heavy refactoring.
//
// TODO hss: There's a separate blinded ID, certificate, and signing key
// for each active time period.
keys: (),
/// Status for each active introduction point for this onion service.
//
// TODO HSS: This might want to be a generational arena, and might want to be
// use a different map for each descriptor epoch. Feel free to refactor!
intro_points: Vec<IntroPointState>,
/// Status for our onion service descriptor
desc_status: DescUploadHistory,
} }
/// Information about encryption-based authentication.
struct DescEncryptionAuth {
/// A list of the public keys for which we should encrypt our
/// descriptor.
//
// TODO HSS: maybe this should instead be a place to find the keys, so that
// we can reload them on change?
//
// TODO HSS: maybe this should instead be part of our configuration
keys: Vec<curve25519::PublicKey>,
}
/// Current history and status for our descriptor uploads.
///
// TODO HSS: Remember, there are *multiple simultaneous variants* of our
// descriptor. we will probably need to make this structure different.
struct DescUploadHistory {
/// When did we last rebuild our descriptors?
last_rebuilt: Instant,
/// Each current descriptor that we need to try to maintain and upload.
descriptors: HashMap<HsBlindIdKey, String>,
/// Status of uploading each descriptor to each HsDir.
//
// Note that is possible that multiple descriptors will need to be uploaded
// to the same HsDir. When this happens, we MUST use separate circuits to
// uplaod them.
target_status: HashMap<HsBlindIdKey, HashMap<RelayIds, RetryState>>,
}
/// State of uploading a single descriptor
struct RetryState {
// TODO HSS: implement this as needed.
}
/// State of a current introduction point.
struct IntroPointState {
// TODO HSS: use diziet's structures from `hssvc-ipt-algorithms.md` once those are more settled.
}
/// Identifier for a single introduction point of an onion point.
//
// TODO HSS maybe use a nicer type, like a generational arena index.
#[derive(Debug, Clone)]
pub(crate) struct IntroPointId(RelayIds);
impl<R: Runtime> OnionService<R> { impl<R: Runtime> OnionService<R> {
/// Create (but do not launch) a new onion service. /// Create (but do not launch) a new onion service.
pub fn new(config: (), netdir_provider: (), circmgr: ()) -> Self { pub fn new(config: (), netdir_provider: (), circmgr: ()) -> Self {
@ -72,6 +157,8 @@ impl<R: Runtime> OnionService<R> {
// changes? Or use a publish-based watcher? // changes? Or use a publish-based watcher?
/// Tell this onion service to begin running. /// Tell this onion service to begin running.
//
// TODO HSS: Probably return an `impl Stream<RendRequest>`.
pub fn launch(&self) -> Result<()> { pub fn launch(&self) -> Result<()> {
todo!() // TODO hss todo!() // TODO hss

View File

@ -0,0 +1,114 @@
//! IPT Establisher
//!
//! Responsible for maintaining and establishing one introduction point.
//!
//! TODO HSS: move docs from `hssvc-ipt-algorithm.md`
#![allow(clippy::needless_pass_by_value)] // TODO HSS remove
use std::sync::Arc;
use futures::channel::mpsc;
use tor_circmgr::hspool::HsCircPool;
use tor_netdir::{NetDirProvider, Relay};
use tor_rtcompat::Runtime;
use crate::RendRequest;
/// Handle onto the task which is establishing and maintaining one IPT
pub(crate) struct IptEstablisher {}
/// When the `IptEstablisher` is dropped it is torn down
///
/// Synchronously
///
/// * No rendezvous requests will be accepted
/// that arrived after `Drop::drop` returns.
///
/// Asynchronously
///
/// * Circuits constructed for this IPT are torn down
/// * The `rend_reqs` sink is closed (dropped)
/// * `IptStatusStatus::Faulty` will be indicated
impl Drop for IptEstablisher {
fn drop(&mut self) {
todo!()
}
}
/// An error from trying to create in introduction point establisher.
///
/// TODO HSS: This is probably too narrow a definition; do something else
/// instead.
#[derive(Clone, Debug, thiserror::Error)]
pub(crate) enum IptError {}
impl IptEstablisher {
/// Try to set up, and maintain, an IPT at `Relay`
///
/// Rendezvous requests will be rejected
pub(crate) fn new<R: Runtime>(
circ_pool: Arc<HsCircPool<R>>,
dirprovider: Arc<dyn NetDirProvider>,
relay: &Relay<'_>,
// Not a postage::watch since we want to count `Good` to `Faulty`
// transitions
//
// (The alternative would be to count them as part of this structure and
// use a postage watch.)
//
// bounded sender with a fixed small bound; OK to stall waiting for manager to catch up
status: mpsc::Sender<IptStatus>,
// TODO HSS: this needs to take some configuration
) -> Result<Self, IptError> {
todo!()
}
/// Begin accepting connections from this introduction point.
//
// TODO HSS: Perhaps we want to provide rend_reqs as part of the
// new() API instead. If we do, we must make sure there's a way to
// turn requests on and off, so that we can say "now we have advertised this
// so requests are okay."
pub(crate) fn start_accepting(&self, rend_reqs: mpsc::Sender<RendRequest>) {
todo!()
}
}
/// The current status of an introduction point, as defined in
/// `hssvc-ipt-algorithms.md`.
///
/// TODO HSS Make that file unneeded.
#[derive(Clone, Debug)]
pub(crate) enum IptStatusStatus {
/// We are (re)establishing our connection to the IPT
///
/// But we don't think there's anything wrong with it.
Establishing,
/// The IPT is established and ready to accept rendezvous requests
Good,
/// We don't have the IPT and it looks like it was the IPT's fault
Faulty,
}
/// `Err(IptWantsToRetire)` indicates that the IPT Establisher wants to retire this IPT
///
/// This happens when the IPT has had (too) many rendezvous requests.
#[derive(Clone, Debug)]
pub(crate) struct IptWantsToRetire;
/// The current status of an introduction point.
#[derive(Clone, Debug)]
pub(crate) struct IptStatus {
/// The current state of this introduction point as defined by
/// `hssvc-ipt-algorithms.md`.
///
/// TODO HSS Make that file unneeded.
pub(crate) status: IptStatusStatus,
/// The current status of whether this introduction point circuit wants to be
/// retired based on having processed too many requests.
pub(crate) wants_to_retire: Result<(), IptWantsToRetire>,
}

View File

@ -0,0 +1,98 @@
//! Publish and maintain onion service descriptors
#![allow(clippy::needless_pass_by_value)] // TODO HSS REMOVE.
use std::sync::Arc;
use tor_netdir::NetDirProvider;
use tor_rtcompat::Runtime;
/// A handle for the Hsdir Publisher for an onion service.
///
/// This handle represents a set of tasks that identify the hsdirs for each
/// releavant time period, construct descriptors, publish them, and keep them
/// up-to-date.
pub(crate) struct Publisher {
// TODO HSS: Write the contents here.
//
// I'm assuming that each Publisher knows its current keys, keeps track of
// the current relevant time periods, and knows the current
// status for uploading to each HsDir.
//
// Some of these contents may actually wind up belonging to a reactor
// task.
//
/// A source for new network directories that we use to determine
/// our HsDirs.
dir_provider: Arc<dyn NetDirProvider>,
}
/// An error from creating or talkign with a Publisher.
#[derive(Clone, Debug, thiserror::Error)]
pub(crate) enum PublisherError {}
impl Publisher {
/// Create and launch a new publisher.
///
/// When it launches, it will know no keys or introduction points,
/// and will therefore not upload any descriptors.
///
#[allow(clippy::unnecessary_wraps)] // TODO HSS REMOVE
pub(crate) fn new<R: Runtime>(
runtime: R,
dir_provider: Arc<dyn NetDirProvider>,
) -> Result<Self, PublisherError> {
// TODO: Do we really want to launch now, or later?
Ok(Self { dir_provider })
}
/// Inform this publisher that its set of keys has changed.
///
/// TODO HSS: Either this needs to take new keys as an argument, or there
/// needs to be a source of keys (including public keys) in Publisher.
pub(crate) fn new_hs_keys(&self, keys: ()) {
todo!()
}
/// Inform this publisher that the set of introduction points has changed.
///
/// TODO HSS: Either this needs to take new intropoints as an argument,
/// or there needs to be a source of intro points in the Publisher.
pub(crate) fn new_intro_points(&self, ipts: ()) {
todo!()
}
/// Return our current status.
//
// TODO HSS: There should also be a postage::Watcher -based stream of status
// change events.
pub(crate) fn status(&self) -> PublisherStatus {
todo!()
}
// TODO HSS: We may also need to update descriptors based on configuration
// or authentication changes.
}
/// Current status of our attempts to publish an onion service descriptor.
#[derive(Debug, Clone)]
pub(crate) struct PublisherStatus {
// TODO HSS add fields
}
//
// Our main loop has to look something like:
// Whenever time period or keys or netdir changes: Check whether our list of
// HsDirs has changed. If it is, add and/or remove hsdirs as needed.
// "when learning about new keys, new intro points, or new configurations,
// or whenever the time period changes: Mark descriptors dirty."
// Whenever descriptors are dirty, we have enough info to generate
// descriptors, and we aren't upload-rate-limited: Generate new descriptors
// and mark descriptors clean. Mark all hsdirs as needing new versions of
// this descriptor.
// While any hsdir does not have the latest version of its any descriptor:
// upload it. Retry with usual timeouts on failure."