tor_basic_utils/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![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_time_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)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45#![allow(clippy::collapsible_if)] // See arti#2342
46#![deny(clippy::unused_async)]
47//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
48
49use std::collections::BinaryHeap;
50use std::fmt;
51use std::mem;
52use std::ops::{RangeInclusive, RangeToInclusive};
53use std::path::Path;
54use std::time::Duration;
55
56pub mod error_sources;
57pub mod intern;
58pub mod iter;
59pub mod n_key_list;
60pub mod n_key_set;
61pub mod rand_hostname;
62pub mod rangebounds;
63pub mod retry;
64pub mod test_rng;
65
66mod byte_qty;
67pub use byte_qty::ByteQty;
68
69pub use paste::paste;
70
71use rand::Rng;
72
73/// Sealed
74mod sealed {
75 /// Sealed
76 pub trait Sealed {}
77}
78use sealed::Sealed;
79
80// ----------------------------------------------------------------------
81
82/// Function with the signature of `Debug::fmt` that just prints `".."`
83///
84/// ```
85/// use educe::Educe;
86/// use tor_basic_utils::skip_fmt;
87///
88/// #[derive(Educe, Default)]
89/// #[educe(Debug)]
90/// struct Wombat {
91/// visible: usize,
92///
93/// #[educe(Debug(method = "skip_fmt"))]
94/// invisible: [u8; 2],
95/// }
96///
97/// assert_eq!( format!("{:?}", &Wombat::default()),
98/// "Wombat { visible: 0, invisible: .. }" );
99/// ```
100pub fn skip_fmt<T>(_: &T, f: &mut fmt::Formatter) -> fmt::Result {
101 /// Inner function avoids code bloat due to generics
102 fn inner(f: &mut fmt::Formatter) -> fmt::Result {
103 write!(f, "..")
104 }
105 inner(f)
106}
107
108// ----------------------------------------------------------------------
109
110/// Extension trait to provide `.strip_suffix_ignore_ascii_case()` etc.
111// Using `.as_ref()` as a supertrait lets us make the method a provided one.
112pub trait StrExt: AsRef<str> {
113 /// Like `str.strip_suffix()` but ASCII-case-insensitive
114 fn strip_suffix_ignore_ascii_case(&self, suffix: &str) -> Option<&str> {
115 let whole = self.as_ref();
116 let suffix_start = whole.len().checked_sub(suffix.len())?;
117 whole[suffix_start..]
118 .eq_ignore_ascii_case(suffix)
119 .then(|| &whole[..suffix_start])
120 }
121
122 /// Like `str.ends_with()` but ASCII-case-insensitive
123 fn ends_with_ignore_ascii_case(&self, suffix: &str) -> bool {
124 self.strip_suffix_ignore_ascii_case(suffix).is_some()
125 }
126}
127impl StrExt for str {}
128
129// ----------------------------------------------------------------------
130
131/// Extension trait to provide `.gen_range_checked()`
132pub trait RngExt: Rng {
133 /// Generate a random value in the given range.
134 ///
135 /// This function is optimised for the case that only a single sample is made from the given range. See also the [`Uniform`](rand::distr::uniform::Uniform) distribution type which may be faster if sampling from the same range repeatedly.
136 ///
137 /// If the supplied range is empty, returns `None`.
138 ///
139 /// (This is a non-panicking version of [`Rng::gen_range`].)
140 ///
141 /// ### Example
142 ///
143 /// ```
144 /// use tor_basic_utils::RngExt as _;
145 //
146 // Fake plastic imitation tor_error, since that's actually higher up the stack
147 /// # #[macro_use]
148 /// # mod tor_error {
149 /// # #[derive(Debug)]
150 /// # pub struct Bug;
151 /// # pub fn internal() {} // makes `use` work
152 /// # }
153 /// # macro_rules! internal { { $x:expr } => { Bug } }
154 //
155 /// use tor_error::{Bug, internal};
156 ///
157 /// fn choose(slice: &[i32]) -> Result<i32, Bug> {
158 /// let index = rand::rng()
159 /// .gen_range_checked(0..slice.len())
160 /// .ok_or_else(|| internal!("empty slice"))?;
161 /// Ok(slice[index])
162 /// }
163 ///
164 /// assert_eq!(choose(&[42]).unwrap(), 42);
165 /// let _: Bug = choose(&[]).unwrap_err();
166 /// ```
167 //
168 // TODO: We may someday wish to rename this function to random_range_checked,
169 // since gen_range was renamed to random_range in rand 0.9.
170 // Or we might decide to leave it alone.
171 fn gen_range_checked<T, R>(&mut self, range: R) -> Option<T>
172 where
173 T: rand::distr::uniform::SampleUniform,
174 R: rand::distr::uniform::SampleRange<T>,
175 {
176 if range.is_empty() {
177 None
178 } else {
179 #[allow(clippy::disallowed_methods)]
180 Some(Rng::random_range(self, range))
181 }
182 }
183
184 /// Generate a random value in the given upper-bounded-only range.
185 ///
186 /// For use with an inclusive upper-bounded-only range,
187 /// with types that implement `GenRangeInfallible`
188 /// (that necessarily then implement the appropriate `rand` traits).
189 ///
190 /// This function is optimised for the case that only a single sample is made from the given range. See also the [`Uniform`](rand::distr::uniform::Uniform) distribution type which may be faster if sampling from the same range repeatedly.
191 ///
192 /// ### Example
193 ///
194 /// ```
195 /// use std::time::Duration;
196 /// use tor_basic_utils::RngExt as _;
197 ///
198 /// fn stochastic_sleep(max: Duration) {
199 /// let chosen_delay = rand::rng()
200 /// .gen_range_infallible(..=max);
201 /// std::thread::sleep(chosen_delay);
202 /// }
203 /// ```
204 fn gen_range_infallible<T>(&mut self, range: RangeToInclusive<T>) -> T
205 where
206 T: GenRangeInfallible,
207 {
208 self.gen_range_checked(T::lower_bound()..=range.end)
209 .expect("GenRangeInfallible type with an empty lower_bound()..=T range")
210 }
211}
212impl<T: Rng> RngExt for T {}
213
214/// Types that can be infallibly sampled using `gen_range_infallible`
215///
216/// In addition to the supertraits, the implementor of this trait must guarantee that:
217///
218/// `<Self as GenRangeInfallible>::lower_bound() ..= UPPER`
219/// is a nonempty range for every value of `UPPER`.
220//
221// One might think that this trait is wrong because we might want to be able to
222// implement gen_range_infallible for arguments other than RangeToInclusive<T>.
223// However, double-ended ranges are inherently fallible because the actual values
224// might be in the wrong order. Non-inclusive ranges are fallible because the
225// upper bound might be zero, unless a NonZero type is used, which seems like a further
226// complication that we probably don't want to introduce here. That leaves lower-bounded
227// ranges, but those are very rare.
228pub trait GenRangeInfallible: rand::distr::uniform::SampleUniform + Ord
229where
230 RangeInclusive<Self>: rand::distr::uniform::SampleRange<Self>,
231{
232 /// The usual lower bound, for converting a `RangeToInclusive` to a `RangeInclusive`
233 ///
234 /// Only makes sense with types with a sensible lower bound, such as zero.
235 fn lower_bound() -> Self;
236}
237
238impl GenRangeInfallible for Duration {
239 fn lower_bound() -> Self {
240 Duration::ZERO
241 }
242}
243
244// ----------------------------------------------------------------------
245
246/// Implementation of `ErrorKind::NotADirectory` that doesn't require Nightly
247pub trait IoErrorExt: Sealed {
248 /// Is this `io::ErrorKind::NotADirectory` ?
249 fn is_not_a_directory(&self) -> bool;
250}
251impl Sealed for std::io::Error {}
252impl IoErrorExt for std::io::Error {
253 fn is_not_a_directory(&self) -> bool {
254 self.raw_os_error()
255 == Some(
256 #[cfg(target_family = "unix")]
257 libc::ENOTDIR,
258 #[cfg(target_family = "windows")]
259 {
260 /// Obtained from Rust stdlib source code
261 /// See also:
262 /// <https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499->
263 /// (although the documentation is anaemic) and
264 /// <https://github.com/rust-lang/rust/pull/79965>
265 const ERROR_DIRECTORY: i32 = 267;
266 ERROR_DIRECTORY
267 },
268 )
269 }
270}
271
272// ----------------------------------------------------------------------
273
274/// Implementation of `BinaryHeap::retain` that doesn't require Nightly
275pub trait BinaryHeapExt<T> {
276 /// Remove all elements for which `f` returns `false`
277 ///
278 /// Performance is not great right now - the algorithm is `O(n*log(n))`
279 /// where `n` is the number of elements in the heap (not the number removed).
280 ///
281 /// The name is `retain_ext` to avoid a name collision with the unstable function,
282 /// which would require the use of UFCS and make this unergonomic.
283 fn retain_ext<F: FnMut(&T) -> bool>(&mut self, f: F);
284}
285impl<T: Ord> BinaryHeapExt<T> for BinaryHeap<T> {
286 fn retain_ext<F: FnMut(&T) -> bool>(&mut self, f: F) {
287 let items = mem::take(self).into_iter();
288 *self = items.filter(f).collect();
289 }
290}
291
292// ----------------------------------------------------------------------
293
294/// Renaming of `Path::display` as `display_lossy`
295pub trait PathExt: Sealed {
296 /// Display this `Path` as an approximate string, for human consumption in messages
297 ///
298 /// Operating system paths cannot always be faithfully represented as Rust strings,
299 /// because they might not be valid Unicode.
300 ///
301 /// This helper method provides a way to display a string for human users.
302 /// **This may lose information** so should only be used for error messages etc.
303 ///
304 /// This method is exactly the same as [`std::path::Path::display`],
305 /// but with a different and more discouraging name.
306 fn display_lossy(&self) -> std::path::Display<'_>;
307}
308impl Sealed for Path {}
309impl PathExt for Path {
310 #[allow(clippy::disallowed_methods)]
311 fn display_lossy(&self) -> std::path::Display<'_> {
312 self.display()
313 }
314}
315
316// ----------------------------------------------------------------------
317
318/// Define an "accessor trait", which describes structs that have fields of certain types
319///
320/// This can be useful if a large struct, living high up in the dependency graph,
321/// contains fields that lower-lever crates want to be able to use without having
322/// to copy the data about etc.
323///
324/// ```
325/// // imagine this in the lower-level module
326/// pub trait Supertrait {}
327/// use tor_basic_utils::define_accessor_trait;
328/// define_accessor_trait! {
329/// pub trait View: Supertrait {
330/// lorem: String,
331/// ipsum: usize,
332/// +
333/// fn other_accessor(&self) -> bool;
334/// // any other trait items can go here
335/// }
336/// }
337///
338/// fn test_view<V: View>(v: &V) {
339/// assert_eq!(v.lorem(), "sit");
340/// assert_eq!(v.ipsum(), &42);
341/// }
342///
343/// // imagine this in the higher-level module
344/// use derive_more::AsRef;
345/// #[derive(AsRef)]
346/// struct Everything {
347/// #[as_ref] lorem: String,
348/// #[as_ref] ipsum: usize,
349/// dolor: Vec<()>,
350/// }
351/// impl Supertrait for Everything { }
352/// impl View for Everything {
353/// fn other_accessor(&self) -> bool { false }
354/// }
355///
356/// let everything = Everything {
357/// lorem: "sit".into(),
358/// ipsum: 42,
359/// dolor: vec![()],
360/// };
361///
362/// test_view(&everything);
363/// ```
364///
365/// ### Generated code
366///
367/// ```
368/// # pub trait Supertrait { }
369/// pub trait View: AsRef<String> + AsRef<usize> + Supertrait {
370/// fn lorem(&self) -> &String { self.as_ref() }
371/// fn ipsum(&self) -> &usize { self.as_ref() }
372/// }
373/// ```
374#[macro_export]
375macro_rules! define_accessor_trait {
376 {
377 $( #[ $attr:meta ])*
378 $vis:vis trait $Trait:ident $( : $( $Super:path )* )? {
379 $( $accessor:ident: $type:ty, )*
380 $( + $( $rest:tt )* )?
381 }
382 } => {
383 $( #[ $attr ])*
384 $vis trait $Trait: $( core::convert::AsRef<$type> + )* $( $( $Super + )* )?
385 {
386 $(
387 /// Access the field
388 fn $accessor(&self) -> &$type { core::convert::AsRef::as_ref(self) }
389 )*
390 $(
391 $( $rest )*
392 )?
393 }
394 }
395}
396
397// ----------------------------------------------------------------------
398
399/// Helper for assisting with macro "argument" defaulting
400///
401/// ```ignore
402/// macro_coalesce_args!{ [ something ] ... } // => something
403/// macro_coalesce_args!{ [ ], [ other ] ... } // => other
404/// // etc.
405/// ```
406///
407/// ### Usage note
408///
409/// It is generally possible to avoid use of `macro_coalesce_args`, at the cost of
410/// providing many alternative matcher patterns. Using `macro_coalesce_args` can make
411/// it possible to provide a single pattern with the optional items in `$( )?`.
412///
413/// This is valuable because a single pattern with some optional items
414/// makes much better documentation than several patterns which the reader must compare
415/// by eye - and it also simplifies the implementation.
416///
417/// `macro_coalesce_args` takes each of its possible expansions in `[ ]` and returns
418/// the first nonempty one.
419#[macro_export]
420macro_rules! macro_first_nonempty {
421 { [ $($yes:tt)+ ] $($rhs:tt)* } => { $($yes)* };
422 { [ ]$(,)? [ $($otherwise:tt)* ] $($rhs:tt)* } => {
423 $crate::macro_first_nonempty!{ [ $($otherwise)* ] $($rhs)* }
424 };
425}
426
427// ----------------------------------------------------------------------
428
429/// Define `Debug` to print as hex
430///
431/// # Usage
432///
433/// ```ignore
434/// impl_debug_hex! { $type }
435/// impl_debug_hex! { $type . $field_accessor }
436/// impl_debug_hex! { $type , $accessor_fn }
437/// ```
438///
439/// By default, this expects `$type` to implement `AsRef<[u8]>`.
440///
441/// Or, you can supply a series of tokens `$field_accessor`,
442/// which will be used like this: `self.$field_accessor.as_ref()`
443/// to get a `&[u8]`.
444///
445/// Or, you can supply `$accessor: fn(&$type) -> &[u8]`.
446///
447/// # Examples
448///
449/// ```
450/// use tor_basic_utils::impl_debug_hex;
451/// #[derive(Default)]
452/// struct FourBytes([u8; 4]);
453/// impl AsRef<[u8]> for FourBytes { fn as_ref(&self) -> &[u8] { &self.0 } }
454/// impl_debug_hex! { FourBytes }
455///
456/// assert_eq!(
457/// format!("{:?}", FourBytes::default()),
458/// "FourBytes(00000000)",
459/// );
460/// ```
461///
462/// ```
463/// use tor_basic_utils::impl_debug_hex;
464/// #[derive(Default)]
465/// struct FourBytes([u8; 4]);
466/// impl_debug_hex! { FourBytes .0 }
467///
468/// assert_eq!(
469/// format!("{:?}", FourBytes::default()),
470/// "FourBytes(00000000)",
471/// );
472/// ```
473///
474/// ```
475/// use tor_basic_utils::impl_debug_hex;
476/// struct FourBytes([u8; 4]);
477/// impl_debug_hex! { FourBytes, |self_| &self_.0 }
478///
479/// assert_eq!(
480/// format!("{:?}", FourBytes([1,2,3,4])),
481/// "FourBytes(01020304)",
482/// )
483/// ```
484#[macro_export]
485macro_rules! impl_debug_hex {
486 { $type:ty $(,)? } => {
487 $crate::impl_debug_hex! { $type, |self_| <$type as AsRef<[u8]>>::as_ref(&self_) }
488 };
489 { $type:ident . $($accessor:tt)+ } => {
490 $crate::impl_debug_hex! { $type, |self_| self_ . $($accessor)* .as_ref() }
491 };
492 { $type:ty, $obtain:expr $(,)? } => {
493 impl std::fmt::Debug for $type {
494 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
495 use std::fmt::Write;
496 let obtain: fn(&$type) -> &[u8] = $obtain;
497 let bytes: &[u8] = obtain(self);
498 write!(f, "{}(", stringify!($type))?;
499 for b in bytes {
500 write!(f, "{:02x}", b)?;
501 }
502 write!(f, ")")?;
503 Ok(())
504 }
505 }
506 };
507}
508
509// ----------------------------------------------------------------------
510
511/// Helper for defining a struct which can be (de)serialized several ways, including "natively"
512///
513/// Ideally we would have
514/// ```rust ignore
515/// #[derive(Deserialize)]
516/// #[serde(try_from=Possibilities)]
517/// struct Main { /* principal definition */ }
518///
519/// #[derive(Deserialize)]
520/// #[serde(untagged)]
521/// enum Possibilities { Main(Main), Other(OtherRepr) }
522///
523/// #[derive(Deserialize)]
524/// struct OtherRepr { /* other representation we still want to read */ }
525///
526/// impl TryFrom<Possibilities> for Main { /* ... */ }
527/// ```
528///
529/// But the impl for `Possibilities` ends up honouring the `try_from` on `Main`
530/// so is recursive.
531///
532/// We solve that (ab)using serde's remote feature,
533/// on a second copy of the struct definition.
534///
535/// See the Example for instructions.
536/// It is important to **add test cases**
537/// for all the representations you expect to parse and serialise,
538/// since there are easy-to-write bugs,
539/// for example omitting some of the necessary attributes.
540///
541/// # Generated output:
542///
543/// * The original struct definition, unmodified
544/// * `#[derive(Serialize, Deserialize)] struct $main_Raw { }`
545///
546/// The `$main_Raw` struct ought not normally be to constructed anywhere,
547/// and *isn't* convertible to or from the near-identical `$main` struct.
548/// It exists only as a thing to feed to the serde remove derive,
549/// and name in `with=`.
550///
551/// # Example
552///
553/// ```
554/// use serde::{Deserialize, Serialize};
555/// use tor_basic_utils::derive_serde_raw;
556///
557/// derive_serde_raw! {
558/// #[derive(Deserialize, Serialize, Default, Clone, Debug)]
559/// #[serde(try_from="BridgeConfigBuilderSerde", into="BridgeConfigBuilderSerde")]
560/// pub struct BridgeConfigBuilder = "BridgeConfigBuilder" {
561/// transport: Option<String>,
562/// //...
563/// }
564/// }
565///
566/// #[derive(Serialize,Deserialize)]
567/// #[serde(untagged)]
568/// enum BridgeConfigBuilderSerde {
569/// BridgeLine(String),
570/// Dict(#[serde(with="BridgeConfigBuilder_Raw")] BridgeConfigBuilder),
571/// }
572///
573/// impl TryFrom<BridgeConfigBuilderSerde> for BridgeConfigBuilder { //...
574/// # type Error = std::io::Error;
575/// # fn try_from(_: BridgeConfigBuilderSerde) -> Result<Self, Self::Error> { todo!() } }
576/// impl From<BridgeConfigBuilder> for BridgeConfigBuilderSerde { //...
577/// # fn from(_: BridgeConfigBuilder) -> BridgeConfigBuilderSerde { todo!() } }
578/// ```
579#[macro_export]
580macro_rules! derive_serde_raw { {
581 $( #[ $($attrs:meta)* ] )*
582 $vis:vis struct $main:ident=$main_s:literal
583 $($body:tt)*
584} => {
585 $(#[ $($attrs)* ])*
586 $vis struct $main
587 $($body)*
588
589 $crate::paste! {
590 #[allow(non_camel_case_types)]
591 #[derive(Serialize, Deserialize)]
592 #[serde(remote=$main_s)]
593 struct [< $main _Raw >]
594 $($body)*
595 }
596} }
597
598// ----------------------------------------------------------------------
599
600/// Flatten a `Result<Result<T, E>, E>` into a `Result<T, E>`.
601///
602/// See [`Result::flatten`], which is not available
603/// at our current MSRV.
604// TODO MSRV 1.89: When our MSRV is at least 1.89,
605// remove this function and replace uses with `Result::flatten`.
606pub fn flatten<T, E>(x: Result<Result<T, E>, E>) -> Result<T, E> {
607 match x {
608 Ok(Ok(x)) => Ok(x),
609 Err(e) | Ok(Err(e)) => Err(e),
610 }
611}
612
613// ----------------------------------------------------------------------
614
615/// Asserts that the type of the expression implements the given trait.
616///
617/// Example:
618///
619/// ```
620/// # use tor_basic_utils::assert_val_impl_trait;
621/// let x: u32 = 0;
622/// assert_val_impl_trait!(x, Clone);
623/// ```
624#[macro_export]
625macro_rules! assert_val_impl_trait {
626 ($check:expr, $trait:path $(,)?) => {{
627 fn ensure_trait<T: $trait>(_s: &T) {}
628 ensure_trait(&$check);
629 }};
630}
631
632// ----------------------------------------------------------------------
633
634#[cfg(test)]
635mod test {
636 // @@ begin test lint list maintained by maint/add_warning @@
637 #![allow(clippy::bool_assert_comparison)]
638 #![allow(clippy::clone_on_copy)]
639 #![allow(clippy::dbg_macro)]
640 #![allow(clippy::mixed_attributes_style)]
641 #![allow(clippy::print_stderr)]
642 #![allow(clippy::print_stdout)]
643 #![allow(clippy::single_char_pattern)]
644 #![allow(clippy::unwrap_used)]
645 #![allow(clippy::unchecked_time_subtraction)]
646 #![allow(clippy::useless_vec)]
647 #![allow(clippy::needless_pass_by_value)]
648 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
649 use super::*;
650
651 #[test]
652 fn test_strip_suffix_ignore_ascii_case() {
653 assert_eq!(
654 "hi there".strip_suffix_ignore_ascii_case("THERE"),
655 Some("hi ")
656 );
657 assert_eq!("hi here".strip_suffix_ignore_ascii_case("THERE"), None);
658 assert_eq!("THERE".strip_suffix_ignore_ascii_case("there"), Some(""));
659 assert_eq!("hi".strip_suffix_ignore_ascii_case("THERE"), None);
660 }
661}