#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![warn(missing_docs)]
#![warn(noop_method_call)]
#![warn(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::unchecked_duration_subtraction)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::mod_module_files)]
#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::needless_lifetimes)] #![allow(mismatched_lifetime_syntaxes)]
#![allow(non_upper_case_globals)]
#![allow(clippy::upper_case_acronyms)]
use caret::caret_int;
use thiserror::Error;
pub mod named;
caret_int! {
#[derive(Hash,Ord,PartialOrd)]
pub struct ProtoKind(u8) {
Link = 0,
LinkAuth = 1,
Relay = 2,
DirCache = 3,
HSDir = 4,
HSIntro = 5,
HSRend = 6,
Desc = 7,
Microdesc = 8,
Cons = 9,
Padding = 10,
FlowCtrl = 11,
Conflux = 12,
}
}
const N_RECOGNIZED: usize = 13;
const MAX_VER: usize = 63;
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub struct NamedSubver {
kind: ProtoKind,
version: u8,
}
impl NamedSubver {
const fn new(kind: ProtoKind, version: u8) -> Self {
assert!((kind.0 as usize) < N_RECOGNIZED);
assert!((version as usize) <= MAX_VER);
Self { kind, version }
}
}
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub struct NumberedSubver {
kind: ProtoKind,
version: u8,
}
impl NumberedSubver {
pub fn new(kind: impl Into<ProtoKind>, version: u8) -> Self {
Self {
kind: kind.into(),
version,
}
}
pub fn into_parts(self) -> (ProtoKind, u8) {
(self.kind, self.version)
}
}
impl From<NamedSubver> for NumberedSubver {
fn from(value: NamedSubver) -> Self {
Self {
kind: value.kind,
version: value.version,
}
}
}
#[cfg(feature = "tor-bytes")]
impl tor_bytes::Readable for NumberedSubver {
fn take_from(b: &mut tor_bytes::Reader<'_>) -> tor_bytes::Result<Self> {
let kind = b.take_u8()?;
let version = b.take_u8()?;
Ok(Self::new(kind, version))
}
}
#[cfg(feature = "tor-bytes")]
impl tor_bytes::Writeable for NumberedSubver {
fn write_onto<B: tor_bytes::Writer + ?Sized>(&self, b: &mut B) -> tor_bytes::EncodeResult<()> {
b.write_u8(self.kind.into());
b.write_u8(self.version);
Ok(())
}
}
#[derive(Eq, PartialEq, Clone, Debug, Hash, Ord, PartialOrd)]
enum Protocol {
Proto(ProtoKind),
Unrecognized(String),
}
impl Protocol {
fn is_unrecognized(&self, s: &str) -> bool {
match self {
Protocol::Unrecognized(s2) => s2 == s,
_ => false,
}
}
fn to_str(&self) -> &str {
match self {
Protocol::Proto(k) => k.to_str().unwrap_or("<bug>"),
Protocol::Unrecognized(s) => s,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
struct SubprotocolEntry {
proto: Protocol,
supported: u64,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
)]
pub struct Protocols {
recognized: [u64; N_RECOGNIZED],
unrecognized: Vec<SubprotocolEntry>,
}
impl Protocols {
pub fn new() -> Self {
Protocols::default()
}
fn supports_recognized_ver(&self, proto: usize, ver: u8) -> bool {
if usize::from(ver) > MAX_VER {
return false;
}
if proto >= self.recognized.len() {
return false;
}
(self.recognized[proto] & (1 << ver)) != 0
}
fn supports_unrecognized_ver(&self, proto: &str, ver: u8) -> bool {
if usize::from(ver) > MAX_VER {
return false;
}
let ent = self
.unrecognized
.iter()
.find(|ent| ent.proto.is_unrecognized(proto));
match ent {
Some(e) => (e.supported & (1 << ver)) != 0,
None => false,
}
}
pub fn is_empty(&self) -> bool {
self.recognized.iter().all(|v| *v == 0)
&& self.unrecognized.iter().all(|p| p.supported == 0)
}
pub fn supports_known_subver(&self, proto: ProtoKind, ver: u8) -> bool {
self.supports_recognized_ver(proto.get() as usize, ver)
}
pub fn supports_subver(&self, proto: &str, ver: u8) -> bool {
match ProtoKind::from_name(proto) {
Some(p) => self.supports_recognized_ver(p.get() as usize, ver),
None => self.supports_unrecognized_ver(proto, ver),
}
}
pub fn supports_named_subver(&self, protover: NamedSubver) -> bool {
self.supports_known_subver(protover.kind, protover.version)
}
pub fn supports_numbered_subver(&self, protover: NumberedSubver) -> bool {
self.supports_known_subver(protover.kind, protover.version)
}
pub fn difference(&self, other: &Protocols) -> Protocols {
let mut r = Protocols::default();
for i in 0..N_RECOGNIZED {
r.recognized[i] = self.recognized[i] & !other.recognized[i];
}
for ent in self.unrecognized.iter() {
let mut ent = ent.clone();
if let Some(other_ent) = other.unrecognized.iter().find(|e| e.proto == ent.proto) {
ent.supported &= !other_ent.supported;
}
if ent.supported != 0 {
r.unrecognized.push(ent);
}
}
r
}
pub fn union(&self, other: &Protocols) -> Protocols {
let mut r = self.clone();
for i in 0..N_RECOGNIZED {
r.recognized[i] |= other.recognized[i];
}
for ent in other.unrecognized.iter() {
if let Some(my_ent) = r.unrecognized.iter_mut().find(|e| e.proto == ent.proto) {
my_ent.supported |= ent.supported;
} else {
r.unrecognized.push(ent.clone());
}
}
r.unrecognized.sort();
r
}
pub fn intersection(&self, other: &Protocols) -> Protocols {
let mut r = Protocols::default();
for i in 0..N_RECOGNIZED {
r.recognized[i] = self.recognized[i] & other.recognized[i];
}
for ent in self.unrecognized.iter() {
if let Some(other_ent) = other.unrecognized.iter().find(|e| e.proto == ent.proto) {
let supported = ent.supported & other_ent.supported;
if supported != 0 {
r.unrecognized.push(SubprotocolEntry {
proto: ent.proto.clone(),
supported,
});
}
}
}
r.unrecognized.sort();
r
}
fn add(&mut self, foundmask: &mut u64, ent: SubprotocolEntry) -> Result<(), ParseError> {
match ent.proto {
Protocol::Proto(k) => {
let idx = k.get() as usize;
assert!(idx < N_RECOGNIZED); let bit = 1 << u64::from(k.get());
if (*foundmask & bit) != 0 {
return Err(ParseError::Duplicate);
}
*foundmask |= bit;
self.recognized[idx] = ent.supported;
}
Protocol::Unrecognized(ref s) => {
if self
.unrecognized
.iter()
.any(|ent| ent.proto.is_unrecognized(s))
{
return Err(ParseError::Duplicate);
}
if ent.supported != 0 {
self.unrecognized.push(ent);
}
}
}
Ok(())
}
}
#[derive(Error, Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum ParseError {
#[error("Protocol version out of range")]
OutOfRange,
#[error("Duplicate protocol entry")]
Duplicate,
#[error("Malformed protocol entry")]
Malformed,
}
fn bitrange(lo: u64, hi: u64) -> u64 {
assert!(lo <= hi && lo <= 63 && hi <= 63);
let mut mask = !0;
mask <<= 63 - hi;
mask >>= 63 - hi + lo;
mask <<= lo;
mask
}
fn is_good_number(n: &str) -> bool {
n.chars().all(|ch| ch.is_ascii_digit()) && !n.starts_with('0')
}
impl std::str::FromStr for SubprotocolEntry {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, ParseError> {
let (name, versions) = {
let eq_idx = s.find('=').ok_or(ParseError::Malformed)?;
(&s[..eq_idx], &s[eq_idx + 1..])
};
let proto = match ProtoKind::from_name(name) {
Some(p) => Protocol::Proto(p),
None => Protocol::Unrecognized(name.to_string()),
};
if versions.is_empty() {
return Ok(SubprotocolEntry {
proto,
supported: 0,
});
}
let mut supported = 0_u64;
for ent in versions.split(',') {
let (lo_s, hi_s) = {
match ent.find('-') {
Some(pos) => (&ent[..pos], &ent[pos + 1..]),
None => (ent, ent),
}
};
if !is_good_number(lo_s) {
return Err(ParseError::Malformed);
}
if !is_good_number(hi_s) {
return Err(ParseError::Malformed);
}
let lo: u64 = lo_s.parse().map_err(|_| ParseError::Malformed)?;
let hi: u64 = hi_s.parse().map_err(|_| ParseError::Malformed)?;
if lo > (MAX_VER as u64) || hi > (MAX_VER as u64) {
return Err(ParseError::OutOfRange);
}
if lo > hi {
return Err(ParseError::Malformed);
}
let mask = bitrange(lo, hi);
if (supported & mask) != 0 {
return Err(ParseError::Duplicate);
}
supported |= mask;
}
Ok(SubprotocolEntry { proto, supported })
}
}
impl std::str::FromStr for Protocols {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, ParseError> {
let mut result = Protocols::new();
let mut foundmask = 0_u64;
for ent in s.split(' ') {
if ent.is_empty() {
continue;
}
let s: SubprotocolEntry = ent.parse()?;
result.add(&mut foundmask, s)?;
}
result.unrecognized.sort();
Ok(result)
}
}
fn dumpmask(mut mask: u64) -> String {
fn append(v: &mut Vec<String>, lo: u32, hi: u32) {
if lo == hi {
v.push(lo.to_string());
} else {
v.push(format!("{}-{}", lo, hi));
}
}
let mut result = Vec::new();
let mut shift = 0;
while mask != 0 {
let zeros = mask.trailing_zeros();
mask >>= zeros;
shift += zeros;
let ones = mask.trailing_ones();
append(&mut result, shift, shift + ones - 1);
shift += ones;
if ones == 64 {
break;
}
mask >>= ones;
}
result.join(",")
}
impl std::fmt::Display for Protocols {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut entries = Vec::new();
for (idx, mask) in self.recognized.iter().enumerate() {
if *mask != 0 {
let pk: ProtoKind = (idx as u8).into();
entries.push(format!("{}={}", pk, dumpmask(*mask)));
}
}
for ent in &self.unrecognized {
if ent.supported != 0 {
entries.push(format!(
"{}={}",
ent.proto.to_str(),
dumpmask(ent.supported)
));
}
}
entries.sort();
write!(f, "{}", entries.join(" "))
}
}
impl FromIterator<NamedSubver> for Protocols {
fn from_iter<T: IntoIterator<Item = NamedSubver>>(iter: T) -> Self {
let mut r = Protocols::new();
for named_subver in iter {
let proto_idx = usize::from(named_subver.kind.get());
let proto_ver = named_subver.version;
assert!(proto_idx < N_RECOGNIZED);
assert!(usize::from(proto_ver) <= MAX_VER);
r.recognized[proto_idx] |= 1_u64 << proto_ver;
}
r
}
}
pub mod doc_supported {}
pub mod doc_changing {}
#[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_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use std::str::FromStr;
use super::*;
#[test]
fn test_bitrange() {
assert_eq!(0b1, bitrange(0, 0));
assert_eq!(0b10, bitrange(1, 1));
assert_eq!(0b11, bitrange(0, 1));
assert_eq!(0b1111110000000, bitrange(7, 12));
assert_eq!(!0, bitrange(0, 63));
}
#[test]
fn test_dumpmask() {
assert_eq!("", dumpmask(0));
assert_eq!("0-5", dumpmask(0b111111));
assert_eq!("4-5", dumpmask(0b110000));
assert_eq!("1,4-5", dumpmask(0b110010));
assert_eq!("0-63", dumpmask(!0));
}
#[test]
fn test_canonical() -> Result<(), ParseError> {
fn t(orig: &str, canonical: &str) -> Result<(), ParseError> {
let protos: Protocols = orig.parse()?;
let enc = format!("{}", protos);
assert_eq!(enc, canonical);
Ok(())
}
t("", "")?;
t(" ", "")?;
t("Link=5,6,7,9 Relay=4-7,2", "Link=5-7,9 Relay=2,4-7")?;
t("FlowCtrl= Padding=8,7 Desc=1-5,6-8", "Desc=1-8 Padding=7-8")?;
t("Zelda=7 Gannon=3,6 Link=4", "Gannon=3,6 Link=4 Zelda=7")?;
Ok(())
}
#[test]
fn test_invalid() {
fn t(s: &str) -> ParseError {
let protos: Result<Protocols, ParseError> = s.parse();
assert!(protos.is_err());
protos.err().unwrap()
}
assert_eq!(t("Link=1-100"), ParseError::OutOfRange);
assert_eq!(t("Zelda=100"), ParseError::OutOfRange);
assert_eq!(t("Link=100-200"), ParseError::OutOfRange);
assert_eq!(t("Link=1,1"), ParseError::Duplicate);
assert_eq!(t("Link=1 Link=1"), ParseError::Duplicate);
assert_eq!(t("Link=1 Link=3"), ParseError::Duplicate);
assert_eq!(t("Zelda=1 Zelda=3"), ParseError::Duplicate);
assert_eq!(t("Link=Zelda"), ParseError::Malformed);
assert_eq!(t("Link=6-2"), ParseError::Malformed);
assert_eq!(t("Link=6-"), ParseError::Malformed);
assert_eq!(t("Link=6-,2"), ParseError::Malformed);
assert_eq!(t("Link=1,,2"), ParseError::Malformed);
assert_eq!(t("Link=6-frog"), ParseError::Malformed);
assert_eq!(t("Link=gannon-9"), ParseError::Malformed);
assert_eq!(t("Link Zelda"), ParseError::Malformed);
assert_eq!(t("Link=01"), ParseError::Malformed);
assert_eq!(t("Link=waffle"), ParseError::Malformed);
assert_eq!(t("Link=1_1"), ParseError::Malformed);
}
#[test]
fn test_supports() -> Result<(), ParseError> {
let p: Protocols = "Link=4,5-7 Padding=2 Lonk=1-3,5".parse()?;
assert!(p.supports_known_subver(ProtoKind::Padding, 2));
assert!(!p.supports_known_subver(ProtoKind::Padding, 1));
assert!(p.supports_known_subver(ProtoKind::Link, 6));
assert!(!p.supports_known_subver(ProtoKind::Link, 255));
assert!(!p.supports_known_subver(ProtoKind::Cons, 1));
assert!(!p.supports_known_subver(ProtoKind::Cons, 0));
assert!(p.supports_subver("Link", 6));
assert!(!p.supports_subver("link", 6));
assert!(!p.supports_subver("Cons", 0));
assert!(p.supports_subver("Lonk", 3));
assert!(!p.supports_subver("Lonk", 4));
assert!(!p.supports_subver("lonk", 3));
assert!(!p.supports_subver("Lonk", 64));
Ok(())
}
#[test]
fn test_difference() -> Result<(), ParseError> {
let p1: Protocols = "Link=1-10 Desc=5-10 Relay=1,3,5,7,9 Other=7-60 Mine=1-20".parse()?;
let p2: Protocols = "Link=3-4 Desc=1-6 Relay=2-6 Other=8 Theirs=20".parse()?;
assert_eq!(
p1.difference(&p2),
Protocols::from_str("Link=1-2,5-10 Desc=7-10 Relay=1,7,9 Other=7,9-60 Mine=1-20")?
);
assert_eq!(
p2.difference(&p1),
Protocols::from_str("Desc=1-4 Relay=2,4,6 Theirs=20")?,
);
let nil = Protocols::default();
assert_eq!(p1.difference(&nil), p1);
assert_eq!(p2.difference(&nil), p2);
assert_eq!(nil.difference(&p1), nil);
assert_eq!(nil.difference(&p2), nil);
Ok(())
}
#[test]
fn test_union() -> Result<(), ParseError> {
let p1: Protocols = "Link=1-10 Desc=5-10 Relay=1,3,5,7,9 Other=7-60 Mine=1-20".parse()?;
let p2: Protocols = "Link=3-4 Desc=1-6 Relay=2-6 Other=2,8 Theirs=20".parse()?;
assert_eq!(
p1.union(&p2),
Protocols::from_str(
"Link=1-10 Desc=1-10 Relay=1-7,9 Other=2,7-60 Theirs=20 Mine=1-20"
)?
);
assert_eq!(
p2.union(&p1),
Protocols::from_str(
"Link=1-10 Desc=1-10 Relay=1-7,9 Other=2,7-60 Theirs=20 Mine=1-20"
)?
);
let nil = Protocols::default();
assert_eq!(p1.union(&nil), p1);
assert_eq!(p2.union(&nil), p2);
assert_eq!(nil.union(&p1), p1);
assert_eq!(nil.union(&p2), p2);
Ok(())
}
#[test]
fn test_intersection() -> Result<(), ParseError> {
let p1: Protocols = "Link=1-10 Desc=5-10 Relay=1,3,5,7,9 Other=7-60 Mine=1-20".parse()?;
let p2: Protocols = "Link=3-4 Desc=1-6 Relay=2-6 Other=2,8 Theirs=20".parse()?;
assert_eq!(
p1.intersection(&p2),
Protocols::from_str("Link=3-4 Desc=5-6 Relay=3,5 Other=8")?
);
assert_eq!(
p2.intersection(&p1),
Protocols::from_str("Link=3-4 Desc=5-6 Relay=3,5 Other=8")?
);
let nil = Protocols::default();
assert_eq!(p1.intersection(&nil), nil);
assert_eq!(p2.intersection(&nil), nil);
assert_eq!(nil.intersection(&p1), nil);
assert_eq!(nil.intersection(&p2), nil);
Ok(())
}
#[test]
fn from_iter() {
use named as n;
let empty: [NamedSubver; 0] = [];
let prs: Protocols = empty.iter().copied().collect();
assert_eq!(prs, Protocols::default());
let prs: Protocols = empty.into_iter().collect();
assert_eq!(prs, Protocols::default());
let prs = [
n::LINK_V3,
n::HSDIR_V3,
n::LINK_V4,
n::LINK_V5,
n::CONFLUX_BASE,
]
.into_iter()
.collect::<Protocols>();
assert_eq!(prs, "Link=3-5 HSDir=2 Conflux=1".parse().unwrap());
}
#[test]
fn order_numbered_subvers() {
assert!(NumberedSubver::new(5, 7) < NumberedSubver::new(7, 5));
assert!(NumberedSubver::new(7, 5) < NumberedSubver::new(7, 6));
assert!(NumberedSubver::new(7, 6) < NumberedSubver::new(8, 6));
}
}