1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_duration_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::needless_lifetimes)] #![allow(non_upper_case_globals)]
47#![allow(clippy::upper_case_acronyms)]
48
49use caret::caret_int;
50
51use thiserror::Error;
52
53pub mod named;
54
55caret_int! {
56 #[derive(Hash,Ord,PartialOrd)]
64 pub struct ProtoKind(u16) {
65 Link = 0,
67 LinkAuth = 1,
69 Relay = 2,
72 DirCache = 3,
74 HSDir = 4,
76 HSIntro = 5,
78 HSRend = 6,
80 Desc = 7,
82 MicroDesc = 8,
84 Cons = 9,
86 Padding = 10,
88 FlowCtrl = 11,
90 Conflux = 12,
92 }
93}
94
95const N_RECOGNIZED: usize = 13;
97
98#[derive(Eq, PartialEq, Copy, Clone, Debug)]
100pub struct NamedSubver {
101 kind: ProtoKind,
103 version: u8,
105}
106
107impl NamedSubver {
108 const fn new(kind: ProtoKind, version: u8) -> Self {
110 Self { kind, version }
111 }
112}
113
114#[derive(Eq, PartialEq, Clone, Debug, Hash, Ord, PartialOrd)]
116enum Protocol {
117 Proto(ProtoKind),
119 Unrecognized(String),
121}
122
123impl Protocol {
124 fn is_unrecognized(&self, s: &str) -> bool {
126 match self {
127 Protocol::Unrecognized(s2) => s2 == s,
128 _ => false,
129 }
130 }
131 fn to_str(&self) -> &str {
133 match self {
134 Protocol::Proto(k) => k.to_str().unwrap_or("<bug>"),
135 Protocol::Unrecognized(s) => s,
136 }
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
144struct SubprotocolEntry {
145 proto: Protocol,
147 supported: u64,
150}
151
152#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
163pub struct Protocols {
164 recognized: [u64; N_RECOGNIZED],
166 unrecognized: Vec<SubprotocolEntry>,
168}
169
170impl Protocols {
171 pub fn new() -> Self {
173 Protocols::default()
174 }
175 fn supports_recognized_ver(&self, proto: usize, ver: u8) -> bool {
178 if ver > 63 {
179 return false;
180 }
181 if proto >= self.recognized.len() {
182 return false;
183 }
184 (self.recognized[proto] & (1 << ver)) != 0
185 }
186 fn supports_unrecognized_ver(&self, proto: &str, ver: u8) -> bool {
192 if ver > 63 {
193 return false;
194 }
195 let ent = self
196 .unrecognized
197 .iter()
198 .find(|ent| ent.proto.is_unrecognized(proto));
199 match ent {
200 Some(e) => (e.supported & (1 << ver)) != 0,
201 None => false,
202 }
203 }
204 pub fn supports_known_subver(&self, proto: ProtoKind, ver: u8) -> bool {
217 self.supports_recognized_ver(proto.get() as usize, ver)
218 }
219 pub fn supports_subver(&self, proto: &str, ver: u8) -> bool {
232 match ProtoKind::from_name(proto) {
233 Some(p) => self.supports_recognized_ver(p.get() as usize, ver),
234 None => self.supports_unrecognized_ver(proto, ver),
235 }
236 }
237
238 pub fn supports_named_subver(&self, protover: NamedSubver) -> bool {
247 self.supports_known_subver(protover.kind, protover.version)
248 }
249
250 fn add(&mut self, foundmask: &mut u64, ent: SubprotocolEntry) -> Result<(), ParseError> {
256 match ent.proto {
257 Protocol::Proto(k) => {
258 let idx = k.get() as usize;
259 let bit = 1 << u64::from(k.get());
260 if (*foundmask & bit) != 0 {
261 return Err(ParseError::Duplicate);
262 }
263 *foundmask |= bit;
264 self.recognized[idx] = ent.supported;
265 }
266 Protocol::Unrecognized(ref s) => {
267 if self
268 .unrecognized
269 .iter()
270 .any(|ent| ent.proto.is_unrecognized(s))
271 {
272 return Err(ParseError::Duplicate);
273 }
274 self.unrecognized.push(ent);
275 }
276 }
277 Ok(())
278 }
279}
280
281#[derive(Error, Debug, PartialEq, Eq, Clone)]
283#[non_exhaustive]
284pub enum ParseError {
285 #[error("Protocol version out of range")]
287 OutOfRange,
288 #[error("Duplicate protocol entry")]
290 Duplicate,
291 #[error("Malformed protocol entry")]
293 Malformed,
294}
295
296fn bitrange(lo: u64, hi: u64) -> u64 {
309 assert!(lo <= hi && lo <= 63 && hi <= 63);
310 let mut mask = !0;
311 mask <<= 63 - hi;
312 mask >>= 63 - hi + lo;
313 mask <<= lo;
314 mask
315}
316
317fn is_good_number(n: &str) -> bool {
321 n.chars().all(|ch| ch.is_ascii_digit()) && !n.starts_with('0')
322}
323
324impl std::str::FromStr for SubprotocolEntry {
328 type Err = ParseError;
329
330 fn from_str(s: &str) -> Result<Self, ParseError> {
331 let (name, versions) = {
333 let eq_idx = s.find('=').ok_or(ParseError::Malformed)?;
334 (&s[..eq_idx], &s[eq_idx + 1..])
335 };
336 let proto = match ProtoKind::from_name(name) {
338 Some(p) => Protocol::Proto(p),
339 None => Protocol::Unrecognized(name.to_string()),
340 };
341 if versions.is_empty() {
342 return Ok(SubprotocolEntry {
346 proto,
347 supported: 0,
348 });
349 }
350 let mut supported = 0_u64;
352 for ent in versions.split(',') {
353 let (lo_s, hi_s) = {
357 match ent.find('-') {
358 Some(pos) => (&ent[..pos], &ent[pos + 1..]),
359 None => (ent, ent),
360 }
361 };
362 if !is_good_number(lo_s) {
363 return Err(ParseError::Malformed);
364 }
365 if !is_good_number(hi_s) {
366 return Err(ParseError::Malformed);
367 }
368 let lo: u64 = lo_s.parse().map_err(|_| ParseError::Malformed)?;
369 let hi: u64 = hi_s.parse().map_err(|_| ParseError::Malformed)?;
370 if lo > 63 || hi > 63 {
372 return Err(ParseError::OutOfRange);
373 }
374 if lo > hi {
375 return Err(ParseError::Malformed);
376 }
377 let mask = bitrange(lo, hi);
378 if (supported & mask) != 0 {
380 return Err(ParseError::Duplicate);
381 }
382 supported |= mask;
384 }
385 Ok(SubprotocolEntry { proto, supported })
386 }
387}
388
389impl std::str::FromStr for Protocols {
402 type Err = ParseError;
403
404 fn from_str(s: &str) -> Result<Self, ParseError> {
405 let mut result = Protocols::new();
406 let mut foundmask = 0_u64;
407 for ent in s.split(' ') {
408 if ent.is_empty() {
409 continue;
410 }
411
412 let s: SubprotocolEntry = ent.parse()?;
413 result.add(&mut foundmask, s)?;
414 }
415 result.unrecognized.sort();
416 Ok(result)
417 }
418}
419
420fn dumpmask(mut mask: u64) -> String {
434 fn append(v: &mut Vec<String>, lo: u32, hi: u32) {
436 if lo == hi {
437 v.push(lo.to_string());
438 } else {
439 v.push(format!("{}-{}", lo, hi));
440 }
441 }
442 let mut result = Vec::new();
445 let mut shift = 0;
452 while mask != 0 {
453 let zeros = mask.trailing_zeros();
454 mask >>= zeros;
455 shift += zeros;
456 let ones = mask.trailing_ones();
457 append(&mut result, shift, shift + ones - 1);
458 shift += ones;
459 if ones == 64 {
460 break;
463 }
464 mask >>= ones;
465 }
466 result.join(",")
467}
468
469impl std::fmt::Display for Protocols {
479 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
480 let mut entries = Vec::new();
481 for (idx, mask) in self.recognized.iter().enumerate() {
482 if *mask != 0 {
483 let pk: ProtoKind = (idx as u16).into();
484 entries.push(format!("{}={}", pk, dumpmask(*mask)));
485 }
486 }
487 for ent in &self.unrecognized {
488 if ent.supported != 0 {
489 entries.push(format!(
490 "{}={}",
491 ent.proto.to_str(),
492 dumpmask(ent.supported)
493 ));
494 }
495 }
496 entries.sort();
498 write!(f, "{}", entries.join(" "))
499 }
500}
501
502#[cfg(test)]
503mod test {
504 #![allow(clippy::bool_assert_comparison)]
506 #![allow(clippy::clone_on_copy)]
507 #![allow(clippy::dbg_macro)]
508 #![allow(clippy::mixed_attributes_style)]
509 #![allow(clippy::print_stderr)]
510 #![allow(clippy::print_stdout)]
511 #![allow(clippy::single_char_pattern)]
512 #![allow(clippy::unwrap_used)]
513 #![allow(clippy::unchecked_duration_subtraction)]
514 #![allow(clippy::useless_vec)]
515 #![allow(clippy::needless_pass_by_value)]
516 use super::*;
518
519 #[test]
520 fn test_bitrange() {
521 assert_eq!(0b1, bitrange(0, 0));
522 assert_eq!(0b10, bitrange(1, 1));
523 assert_eq!(0b11, bitrange(0, 1));
524 assert_eq!(0b1111110000000, bitrange(7, 12));
525 assert_eq!(!0, bitrange(0, 63));
526 }
527
528 #[test]
529 fn test_dumpmask() {
530 assert_eq!("", dumpmask(0));
531 assert_eq!("0-5", dumpmask(0b111111));
532 assert_eq!("4-5", dumpmask(0b110000));
533 assert_eq!("1,4-5", dumpmask(0b110010));
534 assert_eq!("0-63", dumpmask(!0));
535 }
536
537 #[test]
538 fn test_canonical() -> Result<(), ParseError> {
539 fn t(orig: &str, canonical: &str) -> Result<(), ParseError> {
540 let protos: Protocols = orig.parse()?;
541 let enc = format!("{}", protos);
542 assert_eq!(enc, canonical);
543 Ok(())
544 }
545
546 t("", "")?;
547 t(" ", "")?;
548 t("Link=5,6,7,9 Relay=4-7,2", "Link=5-7,9 Relay=2,4-7")?;
549 t("FlowCtrl= Padding=8,7 Desc=1-5,6-8", "Desc=1-8 Padding=7-8")?;
550 t("Zelda=7 Gannon=3,6 Link=4", "Gannon=3,6 Link=4 Zelda=7")?;
551
552 Ok(())
553 }
554
555 #[test]
556 fn test_invalid() {
557 fn t(s: &str) -> ParseError {
558 let protos: Result<Protocols, ParseError> = s.parse();
559 assert!(protos.is_err());
560 protos.err().unwrap()
561 }
562
563 assert_eq!(t("Link=1-100"), ParseError::OutOfRange);
564 assert_eq!(t("Zelda=100"), ParseError::OutOfRange);
565 assert_eq!(t("Link=100-200"), ParseError::OutOfRange);
566
567 assert_eq!(t("Link=1,1"), ParseError::Duplicate);
568 assert_eq!(t("Link=1 Link=1"), ParseError::Duplicate);
569 assert_eq!(t("Link=1 Link=3"), ParseError::Duplicate);
570 assert_eq!(t("Zelda=1 Zelda=3"), ParseError::Duplicate);
571
572 assert_eq!(t("Link=Zelda"), ParseError::Malformed);
573 assert_eq!(t("Link=6-2"), ParseError::Malformed);
574 assert_eq!(t("Link=6-"), ParseError::Malformed);
575 assert_eq!(t("Link=6-,2"), ParseError::Malformed);
576 assert_eq!(t("Link=1,,2"), ParseError::Malformed);
577 assert_eq!(t("Link=6-frog"), ParseError::Malformed);
578 assert_eq!(t("Link=gannon-9"), ParseError::Malformed);
579 assert_eq!(t("Link Zelda"), ParseError::Malformed);
580
581 assert_eq!(t("Link=01"), ParseError::Malformed);
582 assert_eq!(t("Link=waffle"), ParseError::Malformed);
583 assert_eq!(t("Link=1_1"), ParseError::Malformed);
584 }
585
586 #[test]
587 fn test_supports() -> Result<(), ParseError> {
588 let p: Protocols = "Link=4,5-7 Padding=2 Lonk=1-3,5".parse()?;
589
590 assert!(p.supports_known_subver(ProtoKind::Padding, 2));
591 assert!(!p.supports_known_subver(ProtoKind::Padding, 1));
592 assert!(p.supports_known_subver(ProtoKind::Link, 6));
593 assert!(!p.supports_known_subver(ProtoKind::Link, 255));
594 assert!(!p.supports_known_subver(ProtoKind::Cons, 1));
595 assert!(!p.supports_known_subver(ProtoKind::Cons, 0));
596 assert!(p.supports_subver("Link", 6));
597 assert!(!p.supports_subver("link", 6));
598 assert!(!p.supports_subver("Cons", 0));
599 assert!(p.supports_subver("Lonk", 3));
600 assert!(!p.supports_subver("Lonk", 4));
601 assert!(!p.supports_subver("lonk", 3));
602 assert!(!p.supports_subver("Lonk", 64));
603
604 Ok(())
605 }
606}