From 42f44fd45868bcea15991f87d7842de88584dcf5 Mon Sep 17 00:00:00 2001 From: Clara Engler Date: Tue, 16 Jun 2026 12:17:36 +0200 Subject: [PATCH 1/8] tor-netdoc: Make some policy methods const This commit makes some policy related constructor functions constant, so we can use them in const expressions, which will be required for the next commit. --- crates/tor-netdoc/src/types/policy.rs | 4 ++-- crates/tor-netdoc/src/types/policy/addrpolicy.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/tor-netdoc/src/types/policy.rs b/crates/tor-netdoc/src/types/policy.rs index 0e342aea7d..a538ba3fe5 100644 --- a/crates/tor-netdoc/src/types/policy.rs +++ b/crates/tor-netdoc/src/types/policy.rs @@ -81,13 +81,13 @@ pub struct PortRange { impl PortRange { /// Create a new port range spanning from lo to hi, asserting that /// the correct invariants hold. - fn new_unchecked(lo: u16, hi: u16) -> Self { + const fn new_unchecked(lo: u16, hi: u16) -> Self { assert!(lo != 0); assert!(lo <= hi); PortRange { lo, hi } } /// Create a port range containing all ports. - pub fn new_all() -> Self { + pub const fn new_all() -> Self { PortRange::new_unchecked(1, 65535) } /// Create a new PortRange. diff --git a/crates/tor-netdoc/src/types/policy/addrpolicy.rs b/crates/tor-netdoc/src/types/policy/addrpolicy.rs index e81d4eb696..e2e0914660 100644 --- a/crates/tor-netdoc/src/types/policy/addrpolicy.rs +++ b/crates/tor-netdoc/src/types/policy/addrpolicy.rs @@ -161,7 +161,7 @@ pub struct AddrPortPattern { impl AddrPortPattern { /// Return an AddrPortPattern matching all targets. - pub fn new_all() -> Self { + pub const fn new_all() -> Self { Self { pattern: IpPattern::Star, ports: PortRange::new_all(), -- GitLab From 0471dcaef82039f1e8ac5c085f79cd041bbb0aa1 Mon Sep 17 00:00:00 2001 From: Clara Engler Date: Tue, 16 Jun 2026 12:30:25 +0200 Subject: [PATCH 2/8] tor-netdoc: Add NetdocEncodableFields for AddrPolicy This commit implements NetdocEncodableFields for AddrPolicy in a non-trivial fashion. A unit test will be added in a moment. --- crates/tor-netdoc/semver.md | 1 + .../tor-netdoc/src/types/policy/addrpolicy.rs | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/crates/tor-netdoc/semver.md b/crates/tor-netdoc/semver.md index acd988c26a..2d136d3f62 100644 --- a/crates/tor-netdoc/semver.md +++ b/crates/tor-netdoc/semver.md @@ -1,3 +1,4 @@ +ADDED: `NetdocEncodableFields` for `AddrPolicy` ADDED: `encode::ItemArgument` for `SpFingerprint` ADDED: `ItemValueEncodable` for `ExtraInfoDigests` ADDED: `ItemValueEncodable` for `RouterDescIntroItem` diff --git a/crates/tor-netdoc/src/types/policy/addrpolicy.rs b/crates/tor-netdoc/src/types/policy/addrpolicy.rs index e2e0914660..bb813692a1 100644 --- a/crates/tor-netdoc/src/types/policy/addrpolicy.rs +++ b/crates/tor-netdoc/src/types/policy/addrpolicy.rs @@ -6,6 +6,7 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::str::FromStr; use crate::NormalItemArgument; +use crate::encode::NetdocEncodableFields; use crate::parse2::{ ErrorProblem as EP, ItemArgumentParseable, ItemStream, KeywordRef, NetdocParseableFields, UnparsedItem, @@ -108,6 +109,40 @@ impl NetdocParseableFields for AddrPolicy { } } +impl NetdocEncodableFields for AddrPolicy { + fn encode_fields(&self, out: &mut crate::encode::NetdocEncoder) -> Result<(), tor_error::Bug> { + // The order of this field is significant, meaning we have to emit the + // values as they are. The spec also strongly recommends a trailing + // `accept *:*` or `reject *:*`. To comply with this, we check for + // an existing final rule with an ALL pattern and add a `reject *:*` + // if that is not the case. This is not super nice and ideally we would + // do this somewhere in the type construction, but we cannot do some + // right now because the legacy parser accumulates it as it is. + const ALL: AddrPortPattern = AddrPortPattern::new_all(); + const DEFAULT_DENY: AddrPolicyRule = AddrPolicyRule { + kind: RuleKind::Reject, + pattern: ALL, + }; + + // Add default deny in case of an absent trailing ALL. + let mut rules = self.rules.clone(); + match self.rules.last() { + // Do nothing if there already is a trailing ALL. + Some(AddrPolicyRule { + kind: _, + pattern: ALL, + }) => {} + // Add a default deny to the end. + _ => rules.push(DEFAULT_DENY), + } + + for rule in rules { + out.push_raw_string(&format!("{} {}\n", rule.kind, rule.pattern)); + } + Ok(()) + } +} + /// A single rule in an address policy. /// /// Contains a pattern and what to do with things that match it. -- GitLab From 3b1a1608eb02ebb16dde2740d35a8b9a35f03069 Mon Sep 17 00:00:00 2001 From: Clara Engler Date: Tue, 16 Jun 2026 12:31:14 +0200 Subject: [PATCH 3/8] tor-netdoc: Replace Ignored with () in AddrPolicy test () implements encodable which we will derive soon. --- crates/tor-netdoc/src/types/policy/addrpolicy.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/tor-netdoc/src/types/policy/addrpolicy.rs b/crates/tor-netdoc/src/types/policy/addrpolicy.rs index bb813692a1..669ff2f2fa 100644 --- a/crates/tor-netdoc/src/types/policy/addrpolicy.rs +++ b/crates/tor-netdoc/src/types/policy/addrpolicy.rs @@ -486,10 +486,7 @@ mod test { #[test] fn parse2() { - use crate::{ - parse2::{self, ParseInput}, - types::Ignored, - }; + use crate::parse2::{self, ParseInput}; use derive_deftly::Deftly; const RULES: &str = "\ @@ -506,7 +503,7 @@ mod test { #[derive_deftly(NetdocParseable)] struct Wrapper { #[allow(dead_code)] - intro: Ignored, + intro: (), #[deftly(netdoc(flatten))] ipv4_policy: AddrPolicy, } -- GitLab From aaa55a2923963c4b1423fe7e7be5e0ce3f115ecd Mon Sep 17 00:00:00 2001 From: Clara Engler Date: Tue, 16 Jun 2026 12:31:47 +0200 Subject: [PATCH 4/8] tor-netdoc: Derive NetdocEncodable in unit test --- crates/tor-netdoc/src/types/policy/addrpolicy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/tor-netdoc/src/types/policy/addrpolicy.rs b/crates/tor-netdoc/src/types/policy/addrpolicy.rs index 669ff2f2fa..0ffb884157 100644 --- a/crates/tor-netdoc/src/types/policy/addrpolicy.rs +++ b/crates/tor-netdoc/src/types/policy/addrpolicy.rs @@ -500,7 +500,7 @@ mod test { reject *:*\n"; #[derive(Deftly)] - #[derive_deftly(NetdocParseable)] + #[derive_deftly(NetdocParseable, NetdocEncodable)] struct Wrapper { #[allow(dead_code)] intro: (), -- GitLab From 9989035c4efa6db735693943d2f7abbded7e5fda Mon Sep 17 00:00:00 2001 From: Clara Engler Date: Tue, 16 Jun 2026 12:33:13 +0200 Subject: [PATCH 5/8] tor-netdoc: Round-trip encoding test for AddrPolicy --- crates/tor-netdoc/src/types/policy/addrpolicy.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/tor-netdoc/src/types/policy/addrpolicy.rs b/crates/tor-netdoc/src/types/policy/addrpolicy.rs index 0ffb884157..5cd92c0ada 100644 --- a/crates/tor-netdoc/src/types/policy/addrpolicy.rs +++ b/crates/tor-netdoc/src/types/policy/addrpolicy.rs @@ -361,6 +361,8 @@ mod test { #![allow(clippy::needless_pass_by_value)] #![allow(clippy::string_slice)] // See arti#2571 //! + use crate::encode::{NetdocEncodable, NetdocEncoder}; + use super::*; #[test] @@ -509,7 +511,7 @@ mod test { } let wrapper = parse2::parse_netdoc::(&ParseInput::new(RULES, "")).unwrap(); - let ap = wrapper.ipv4_policy; + let ap = wrapper.ipv4_policy.clone(); assert_eq!( ap.allows_sockaddr(&"1.1.1.1:80".parse().unwrap()), @@ -536,5 +538,10 @@ mod test { ap.allows_sockaddr(&"1.1.1.1:70".parse().unwrap()), Some(RuleKind::Reject) ); + + // Do round-trip encoding. + let mut enc = NetdocEncoder::default(); + wrapper.encode_unsigned(&mut enc).unwrap(); + assert_eq!(RULES, enc.finish().unwrap()); } } -- GitLab From fff60882ec431215ab24fe176919c899cd4b60e1 Mon Sep 17 00:00:00 2001 From: Clara Engler Date: Tue, 16 Jun 2026 12:41:51 +0200 Subject: [PATCH 6/8] tor-netdoc: Add test for default deny encoding of AddrPolicy --- .../tor-netdoc/src/types/policy/addrpolicy.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/tor-netdoc/src/types/policy/addrpolicy.rs b/crates/tor-netdoc/src/types/policy/addrpolicy.rs index 5cd92c0ada..cfb3135836 100644 --- a/crates/tor-netdoc/src/types/policy/addrpolicy.rs +++ b/crates/tor-netdoc/src/types/policy/addrpolicy.rs @@ -543,5 +543,33 @@ mod test { let mut enc = NetdocEncoder::default(); wrapper.encode_unsigned(&mut enc).unwrap(); assert_eq!(RULES, enc.finish().unwrap()); + + // Test default deny. + let accept_all = { + let mut ap = AddrPolicy::new(); + ap.push(RuleKind::Accept, AddrPortPattern::new_all()); + ap + }; + let reject_all = { + let mut ap = AddrPolicy::new(); + ap.push(RuleKind::Reject, AddrPortPattern::new_all()); + ap + }; + + let tests = [ + (accept_all.clone(), "accept *:*"), // do not add default deny to existing + (reject_all.clone(), "reject *:*"), // do not add default deny to existing + (AddrPolicy::new(), "reject *:*"), // add default deny to empty + ]; + for (input, expected_tail) in tests { + let mut enc = NetdocEncoder::default(); + Wrapper { + intro: (), + ipv4_policy: input, + } + .encode_unsigned(&mut enc) + .unwrap(); + assert_eq!(expected_tail, enc.finish().unwrap().lines().last().unwrap()); + } } } -- GitLab From 79266f925b7b1210961b222d7cc418740ff374bb Mon Sep 17 00:00:00 2001 From: Clara Engler Date: Wed, 17 Jun 2026 08:30:12 +0200 Subject: [PATCH 7/8] tor-netdoc: Use chain!() instead of push Replaces a push by using the chain!() macro from itertools. --- crates/tor-netdoc/src/types/policy/addrpolicy.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/tor-netdoc/src/types/policy/addrpolicy.rs b/crates/tor-netdoc/src/types/policy/addrpolicy.rs index cfb3135836..ad4034c5d5 100644 --- a/crates/tor-netdoc/src/types/policy/addrpolicy.rs +++ b/crates/tor-netdoc/src/types/policy/addrpolicy.rs @@ -5,6 +5,8 @@ use std::fmt::Display; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::str::FromStr; +use itertools::chain; + use crate::NormalItemArgument; use crate::encode::NetdocEncodableFields; use crate::parse2::{ @@ -125,18 +127,17 @@ impl NetdocEncodableFields for AddrPolicy { }; // Add default deny in case of an absent trailing ALL. - let mut rules = self.rules.clone(); - match self.rules.last() { + let default_deny = match self.rules.last() { // Do nothing if there already is a trailing ALL. Some(AddrPolicyRule { kind: _, pattern: ALL, - }) => {} + }) => None, // Add a default deny to the end. - _ => rules.push(DEFAULT_DENY), - } + _ => Some(&DEFAULT_DENY), + }; - for rule in rules { + for rule in chain!(&self.rules, default_deny) { out.push_raw_string(&format!("{} {}\n", rule.kind, rule.pattern)); } Ok(()) -- GitLab From fde92835dfb2ff3cc9e3deea0414341def80f367 Mon Sep 17 00:00:00 2001 From: Clara Engler Date: Wed, 17 Jun 2026 08:37:43 +0200 Subject: [PATCH 8/8] tor-netdoc: Use format_args! instead of format! --- crates/tor-netdoc/src/types/policy/addrpolicy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/tor-netdoc/src/types/policy/addrpolicy.rs b/crates/tor-netdoc/src/types/policy/addrpolicy.rs index ad4034c5d5..86c161c0ae 100644 --- a/crates/tor-netdoc/src/types/policy/addrpolicy.rs +++ b/crates/tor-netdoc/src/types/policy/addrpolicy.rs @@ -138,7 +138,7 @@ impl NetdocEncodableFields for AddrPolicy { }; for rule in chain!(&self.rules, default_deny) { - out.push_raw_string(&format!("{} {}\n", rule.kind, rule.pattern)); + out.push_raw_string(&format_args!("{} {}\n", rule.kind, rule.pattern)); } Ok(()) } -- GitLab