feat: dial hints on the account-bound operations for self-resolving hosts

This commit is contained in:
randogoth 2026-09-29 12:48:40 +03:00
parent 5559fcce31
commit 28f13641a3
3 changed files with 45 additions and 23 deletions

View file

@ -217,6 +217,7 @@ fn valid_username(name: &str) -> Result<(), Error> {
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::b32;
#[test]
fn dial_hint_routes_without_renaming() {
@ -227,14 +228,10 @@ mod tests {
// proof-of-possession and display all read `host`.
assert_eq!(addr.dial.as_deref(), Some("192.0.2.10"));
assert_eq!(addr.host, "example.org");
assert_eq!(addr.short(), "alice@example.org:1961");
// The default port is elided in the short form, as everywhere.
assert_eq!(addr.short(), "alice@example.org");
assert_eq!(Address::parse("alice@example.org").unwrap().dial, None);
}
}
mod tests {
use super::*;
use crate::crypto::b32;
#[test]
fn short_form_and_default_port() {

View file

@ -56,6 +56,20 @@ impl Session {
/// Opens a session, enforcing sec 4's rule about unpinned servers. TCP pins
/// the server's Noise static key; on RNS the destination hash is the pin
/// (RNS.md sec 13.4).
/// The account address with the host's dial hint applied, when the caller
/// resolved DNS itself: account-bound operations re-read the account, so
/// the hint rides along this way rather than in the stored row.
#[cfg(feature = "store")]
fn account_with_dial(store: &Store, dial: Option<&str>) -> Result<Address, Error> {
let addr = store
.account()?
.ok_or(Error::NotRegistered)?;
Ok(match dial {
Some(dial) => addr.with_dial(dial),
None => addr,
})
}
#[cfg(feature = "store")]
pub fn connect(
store: &Store,
@ -286,9 +300,16 @@ pub enum Pushed {
/// An accept or a block only takes effect once the server holds the changed
/// set, so it is pushed now rather than at the next fetch.
#[cfg(feature = "store")]
pub fn push_tokens(store: &Store, account: &Account, timeout: u64) -> Result<Pushed, Error> {
let Some(addr) = store.account()? else {
return Ok(Pushed::NotRegistered);
pub fn push_tokens(
store: &Store,
account: &Account,
dial: Option<&str>,
timeout: u64,
) -> Result<Pushed, Error> {
let addr = match account_with_dial(store, dial) {
Ok(addr) => addr,
Err(Error::NotRegistered) => return Ok(Pushed::NotRegistered),
Err(e) => return Err(e),
};
let mut session = connect(store, &addr, true, timeout)?;
let held = authenticate(session.transport(), &addr.user, account, store)?;
@ -346,10 +367,13 @@ pub struct Rotated {
/// is untouched; only the index moves, and the superseded key stays
/// derivable from it (sec 2).
#[cfg(feature = "store")]
pub fn rotate(store: &Store, account: &Account, timeout: u64) -> Result<Rotated, Error> {
let Some(addr) = store.account()? else {
return Err(Error::NotRegistered);
};
pub fn rotate(
store: &Store,
account: &Account,
dial: Option<&str>,
timeout: u64,
) -> Result<Rotated, Error> {
let addr = account_with_dial(store, dial)?;
if account.index() as usize >= MAX_CHAIN {
return Err(Error::ChainLimit {
index: account.index() + 1,
@ -564,6 +588,9 @@ pub struct FetchOptions<'a> {
pub reset: bool,
/// Network timeout in seconds.
pub timeout: u64,
/// Dial this address instead of the account host, for hosts that
/// resolve DNS themselves; the host keeps its identity roles.
pub dial: Option<&'a str>,
/// Checked between pages and before each envelope; when raised,
/// `fetch_with` stops and returns the partial summary with `cancelled`
/// set. What was processed is accounted for locally — cursor moved or
@ -579,6 +606,7 @@ impl FetchOptions<'_> {
keep,
reset,
timeout,
dial: None,
cancel: None,
}
}
@ -607,9 +635,7 @@ pub fn fetch_with(
account: &Account,
opts: FetchOptions<'_>,
) -> Result<Fetched, Error> {
let Some(addr) = store.account()? else {
return Err(Error::NotRegistered);
};
let addr = account_with_dial(store, opts.dial)?;
if opts.reset {
store.set_cursor(0, &[0u8; ID_LEN])?;
}
@ -761,11 +787,10 @@ pub fn delete(
store: &Store,
account: &Account,
ids: &[[u8; ID_LEN]],
dial: Option<&str>,
timeout: u64,
) -> Result<u16, Error> {
let Some(addr) = store.account()? else {
return Err(Error::NotRegistered);
};
let addr = account_with_dial(store, dial)?;
let mut session = connect(store, &addr, true, timeout)?;
let transport = session.transport();
authenticate(transport, &addr.user, account, store)?;