Skip to main content

tor_config/
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#![allow(clippy::cognitive_complexity)] // See arti#2556
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#![deny(clippy::string_slice)] // See arti#2571
48//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
49
50pub mod cmdline;
51pub mod derive;
52mod err;
53#[macro_use]
54pub mod extend_builder;
55pub mod file_watcher;
56mod flatten;
57pub mod list_builder;
58mod listen;
59pub mod load;
60pub mod map_builder;
61mod misc;
62pub mod mistrust;
63mod mut_cfg;
64pub mod setter_traits;
65pub mod sources;
66#[cfg(feature = "testing")]
67pub mod testing;
68
69#[doc(hidden)]
70pub mod deps {
71    pub use educe;
72    pub use figment;
73    pub use itertools::Itertools;
74    pub use paste::paste;
75    pub use serde;
76    pub use serde_value;
77    pub use tor_basic_utils::{if_empty, macro_first_nonempty};
78}
79
80pub use cmdline::CmdLine;
81pub use err::{ConfigBuildError, ConfigError, ConfigGetValueError, ReconfigureError};
82pub use flatten::{Flatten, Flattenable};
83pub use list_builder::{MultilineListBuilder, MultilineListBuilderError};
84pub use listen::*;
85pub use load::{resolve, resolve_ignore_warnings, resolve_return_results};
86pub use misc::*;
87pub use mut_cfg::MutCfg;
88use serde::de::DeserializeOwned;
89pub use sources::{ConfigurationSource, ConfigurationSources};
90use tor_error::into_internal;
91
92#[doc(hidden)]
93pub use derive_deftly;
94#[doc(hidden)]
95pub use flatten::flattenable_extract_fields;
96
97derive_deftly::template_export_semver_check! { "0.12.1" }
98
99/// A set of configuration fields, represented as a set of nested K=V
100/// mappings.
101///
102/// (This is a wrapper for an underlying type provided by the library that
103/// actually does our configuration.)
104#[derive(Clone, Debug, Default)]
105#[must_use] // to prevent errors from merge_from.
106pub struct ConfigurationTree(figment::Figment);
107
108impl ConfigurationTree {
109    #[cfg(test)]
110    pub(crate) fn get_string(&self, key: &str) -> Result<String, crate::ConfigError> {
111        use figment::value::Value as V;
112        let val = self.0.find_value(key).map_err(ConfigError::from_cfg_err)?;
113        Ok(match val {
114            V::String(_, s) => s.clone(),
115            V::Num(_, n) => n.to_i128().expect("Failed to extract i128").to_string(),
116            _ => format!("{:?}", val),
117        })
118    }
119
120    /// Return the value with a given key as some type that implements Deserialize.
121    ///
122    /// Return `None` if no such value is set in this tree.
123    pub fn get_serde_value<T: DeserializeOwned>(
124        &self,
125        key: &str,
126    ) -> Result<Option<T>, ConfigGetValueError> {
127        use figment::error::{Error as FError, Kind::MissingField};
128        match self.0.extract_inner(key) {
129            Ok(v) => Ok(Some(v)),
130            Err(FError {
131                kind: MissingField(..),
132                ..
133            }) => Ok(None),
134            Err(e) => Err(into_internal!("Unexpected error looking up config value")(e).into()),
135        }
136    }
137
138    /// Override our current tree with the settings in `config`.
139    ///
140    /// `config` must be implement [`Serialize`](serde::Serialize),
141    /// and must serialize to a map.
142    ///
143    /// This operation follows the same as are used when reading
144    /// multiple configuration files in sequence,
145    /// where option settings in later files replace earlier ones.
146    #[allow(clippy::unnecessary_wraps)]
147    pub fn merge_from<T>(&mut self, config: &T) -> Result<(), ConfigError>
148    where
149        T: serde::Serialize,
150    {
151        let provider = figment::providers::Serialized::from(config, figment::Profile::Default);
152        let mut orig = figment::Figment::new();
153        std::mem::swap(&mut orig, &mut self.0);
154        self.0 = orig.merge(provider);
155        // Figment::merge handles errors by making the type of the figment itself into an error...
156        // but we don't want our API to rely on that, so we let method returna Result.
157        Ok(())
158    }
159}
160
161/// Rules for reconfiguring a running Arti instance.
162#[derive(Debug, Clone, Copy, Eq, PartialEq)]
163#[non_exhaustive]
164pub enum Reconfigure {
165    /// Perform no reconfiguration unless we can guarantee that all changes will be successful.
166    AllOrNothing,
167    /// Try to reconfigure as much as possible; warn on fields that we cannot reconfigure.
168    WarnOnFailures,
169    /// Don't reconfigure anything: Only check whether we can guarantee that all changes will be successful.
170    CheckAllOrNothing,
171}
172
173impl Reconfigure {
174    /// Called when we see a disallowed attempt to change `field`: either give a ReconfigureError,
175    /// or warn and return `Ok(())`, depending on the value of `self`.
176    pub fn cannot_change<S: AsRef<str>>(self, field: S) -> Result<(), ReconfigureError> {
177        match self {
178            Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
179                Err(ReconfigureError::CannotChange {
180                    field: field.as_ref().to_owned(),
181                })
182            }
183            Reconfigure::WarnOnFailures => {
184                tracing::warn!("Cannot change {} on a running client.", field.as_ref());
185                Ok(())
186            }
187        }
188    }
189
190    /// As `cannot_change`, but return a [`ReconfigureError::CannotChangeToValue`] variant.
191    ///
192    /// `manner` should be an adverbial preprositional phrase,
193    /// like "from on to off" or "while arti is running".
194    pub fn cannot_change_specific<S, T>(self, field: S, manner: T) -> Result<(), ReconfigureError>
195    where
196        S: AsRef<str>,
197        T: AsRef<str>,
198    {
199        match self {
200            Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
201                Err(ReconfigureError::CannotChangeToValue {
202                    field: field.as_ref().to_owned(),
203                    manner: manner.as_ref().to_owned(),
204                })
205            }
206            Reconfigure::WarnOnFailures => {
207                tracing::warn!(
208                    "Cannot change {} {} on a running client.",
209                    field.as_ref(),
210                    manner.as_ref()
211                );
212                Ok(())
213            }
214        }
215    }
216}
217
218/// Resolves an `Option<Option<T>>` (in a builder) into an `Option<T>`
219///
220///  * If the input is `None`, this indicates that the user did not specify a value,
221///    and we therefore use `def` to obtain the default value.
222///
223///  * If the input is `Some(None)`, or `Some(Some(Default::default()))`,
224///    the user has explicitly specified that this config item should be null/none/nothing,
225///    so we return `None`.
226///
227///  * Otherwise the user provided an actual value, and we return `Some` of it.
228///
229/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
230///
231/// For consistency with other APIs in Arti, when using this,
232/// do not pass `setter(strip_option)` to derive_builder.
233///
234/// # ⚠ Stability Warning ⚠
235///
236/// We may significantly change this so that it is an method in an extension trait.
237//
238// This is an annoying AOI right now because you have to write things like
239//     #[builder(field(build = r#"tor_config::resolve_option(&self.dns_port, || None)"#))]
240//     pub(crate) dns_port: Option<u16>,
241// which recapitulates the field name.  That is very much a bug hazard (indeed, in an
242// early version of some of this code I perpetrated precisely that bug).
243// Fixing this involves a derive_builder feature.
244pub fn resolve_option<T, DF>(input: &Option<Option<T>>, def: DF) -> Option<T>
245where
246    T: Clone + Default + PartialEq,
247    DF: FnOnce() -> Option<T>,
248{
249    resolve_option_general(
250        input.as_ref().map(|ov| ov.as_ref()),
251        |v| v == &T::default(),
252        def,
253    )
254}
255
256/// Resolves an `Option<Option<&T>>` (in a builder) into an `Option<T>`, more generally
257///
258/// Like [`resolve_option`], but:
259///
260///  * Doesn't rely on `T` being `Default + PartialEq`
261///    to determine whether it's the sentinel value;
262///    instead, takes `is_sentinel`.
263///
264///  * Takes `Option<Option<&T>>` which is more general, but less like the usual call sites.
265///
266/// # Behavior
267///
268///  * If the input is `None`, this indicates that the user did not specify a value,
269///    and we therefore use `def` to obtain the default value.
270///
271///  * If the input is `Some(None)`, or `Some(Some(v))` where `is_sentinel(v)` returns true,
272///    the user has explicitly specified that this config item should be null/none/nothing,
273///    so we return `None`.
274///
275///  * Otherwise the user provided an actual value, and we return `Some` of it.
276///
277/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
278///
279/// # ⚠ Stability Warning ⚠
280///
281/// We may significantly change this so that it is an method in an extension trait.
282///
283/// # Example
284/// ```
285/// use tor_config::resolve_option_general;
286///
287/// // Use 0 as a sentinel meaning "explicitly clear" in this example
288/// let is_sentinel = |v: &i32| *v == 0;
289///
290/// // No value provided: use default
291/// assert_eq!(
292///     resolve_option_general(None, is_sentinel, || Some(10)),
293///     Some(10),
294/// );
295///
296/// // Explicitly None
297/// assert_eq!(
298///     resolve_option_general(Some(None), is_sentinel, || Some(10)),
299///     None,
300/// );
301///
302/// // Sentinel value (0) -> return None
303/// assert_eq!(
304///     resolve_option_general(Some(Some(&0)), is_sentinel, || Some(10)),
305///     None,
306/// );
307///
308/// // Set to actual value -> return that value
309/// assert_eq!(
310///     resolve_option_general(Some(Some(&5)), is_sentinel, || Some(10)),
311///     Some(5),
312/// );
313/// ```
314pub fn resolve_option_general<T, ISF, DF>(
315    input: Option<Option<&T>>,
316    is_sentinel: ISF,
317    def: DF,
318) -> Option<T>
319where
320    T: Clone,
321    DF: FnOnce() -> Option<T>,
322    ISF: FnOnce(&T) -> bool,
323{
324    match input {
325        None => def(),
326        Some(None) => None,
327        Some(Some(v)) if is_sentinel(v) => None,
328        Some(Some(v)) => Some(v.clone()),
329    }
330}
331
332/// Defines standard impls for a struct with a `Builder`, incl `Default`
333///
334/// **Use this.**  Do not `#[derive(Builder, Default)]`.  That latter approach would produce
335/// wrong answers if builder attributes are used to specify non-`Default` default values.
336///
337/// # Input syntax
338///
339/// ```
340/// use derive_builder::Builder;
341/// use serde::{Deserialize, Serialize};
342/// use tor_config::impl_standard_builder;
343/// use tor_config::ConfigBuildError;
344///
345/// #[derive(Debug, Builder, Clone, Eq, PartialEq)]
346/// #[builder(derive(Serialize, Deserialize, Debug))]
347/// #[builder(build_fn(error = "ConfigBuildError"))]
348/// struct SomeConfigStruct { }
349/// impl_standard_builder! { SomeConfigStruct }
350///
351/// #[derive(Debug, Builder, Clone, Eq, PartialEq)]
352/// struct UnusualStruct { }
353/// impl_standard_builder! { UnusualStruct: !Deserialize + !Builder }
354/// ```
355///
356/// # Requirements
357///
358/// `$Config`'s builder must have default values for all the fields,
359/// or this macro-generated self-test will fail.
360/// This should be OK for all principal elements of our configuration.
361///
362/// `$ConfigBuilder` must have an appropriate `Deserialize` impl.
363///
364/// # Options
365///
366///  * `!Default` suppresses the `Default` implementation, and the corresponding tests.
367///    This should be done within Arti's configuration only for sub-structures which
368///    contain mandatory fields (and are themselves optional).
369///
370///  * `!Deserialize` suppresses the test case involving `Builder: Deserialize`.
371///    This should not be done for structs which are part of Arti's configuration,
372///    but can be appropriate for other types that use [`derive_builder`].
373///
374///  * `!Builder` suppresses the impl of the [`tor_config::load::Builder`](load::Builder) trait
375///    This will be necessary if the error from the builder is not [`ConfigBuildError`].
376///
377/// # Generates
378///
379///  * `impl Default for $Config`
380///  * `impl Builder for $ConfigBuilder`
381///  * a self-test that the `Default` impl actually works
382///  * a test that the `Builder` can be deserialized from an empty [`ConfigurationTree`],
383///    and then built, and that the result is the same as the ordinary default.
384//
385// The implementation munches fake "trait bounds" (`: !Deserialize + !Wombat ...`) off the RHS.
386// We're going to add at least one more option.
387//
388// When run with `!Default`, this only generates a `builder` impl and an impl of
389// the `Resolvable` trait which probably won't be used anywhere.  That may seem
390// like a poor tradeoff (much fiddly macro code to generate a trivial function in
391// a handful of call sites).  However, this means that `impl_standard_builder!`
392// can be used in more places.  That sets a good example: always use the macro.
393//
394// That is a good example because we want `impl_standard_builder!` to be
395// used elsewhere because it generates necessary tests of properties
396// which might otherwise be violated.  When adding code, people add according to the
397// patterns they see.
398//
399// (We, sadly, don't have a good way to *ensure* use of `impl_standard_builder`.)
400#[macro_export]
401macro_rules! impl_standard_builder {
402    // Convert the input into the "being processed format":
403    {
404        $Config:ty $(: $($options:tt)* )?
405    } => { $crate::impl_standard_builder!{
406        // ^Being processed format:
407        @ ( Builder                    )
408          ( default                    )
409          ( extract                    ) $Config    :                 $( $( $options    )* )?
410        //  ~~~~~~~~~~~~~~~              ^^^^^^^    ^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
411        // present iff not !Builder, !Default
412        // present iff not !Default
413        // present iff not !Deserialize  type      always present    options yet to be parsed
414    } };
415    // If !Deserialize is the next option, implement it by making $try_deserialize absent
416    {
417        @ ( $($Builder        :ident)? )
418          ( $($default        :ident)? )
419          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Deserialize $( $options:tt )*
420    } => {  $crate::impl_standard_builder!{
421        @ ( $($Builder              )? )
422          ( $($default              )? )
423          (                            ) $Config    :                    $( $options    )*
424    } };
425    // If !Builder is the next option, implement it by making $Builder absent
426    {
427        @ ( $($Builder        :ident)? )
428          ( $($default        :ident)? )
429          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Builder     $( $options:tt )*
430    } => {  $crate::impl_standard_builder!{
431        @ (                            )
432          ( $($default              )? )
433          ( $($try_deserialize      )? ) $Config    :                    $( $options    )*
434    } };
435    // If !Default is the next option, implement it by making $default absent
436    {
437        @ ( $($Builder        :ident)? )
438          ( $($default        :ident)? )
439          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Default     $( $options:tt )*
440    } => {  $crate::impl_standard_builder!{
441        @ ( $($Builder              )? )
442          (                            )
443          ( $($try_deserialize      )? ) $Config    :                    $( $options    )*
444    } };
445    // Having parsed all options, produce output:
446    {
447        @ ( $($Builder        :ident)? )
448          ( $($default        :ident)? )
449          ( $($try_deserialize:ident)? ) $Config:ty : $(+)?
450    } => { $crate::deps::paste!{
451        impl $Config {
452            /// Returns a fresh, default, builder
453            pub fn builder() -> [< $Config Builder >] {
454                Default::default()
455            }
456        }
457
458        $( // expands iff there was $default, which is always default
459            impl Default for $Config {
460                fn $default() -> Self {
461                    // unwrap is good because one of the test cases above checks that it works!
462                    [< $Config Builder >]::default().build().unwrap()
463                }
464            }
465        )?
466
467        $( // expands iff there was $Builder, which is always Builder
468            impl $crate::load::$Builder for [< $Config Builder >] {
469                type Built = $Config;
470                fn build(&self) -> std::result::Result<$Config, $crate::ConfigBuildError> {
471                    [< $Config Builder >]::build(self)
472                }
473            }
474        )?
475
476        #[test]
477        #[allow(non_snake_case)]
478        fn [< test_impl_Default_for_ $Config >] () {
479            #[allow(unused_variables)]
480            let def = None::<$Config>;
481            $( // expands iff there was $default, which is always default
482                let def = Some($Config::$default());
483            )?
484
485            if let Some(def) = def {
486                $( // expands iff there was $try_deserialize, which is always extract
487                    let empty_config = $crate::deps::figment::Figment::new();
488                    let builder: [< $Config Builder >] = empty_config.$try_deserialize().unwrap();
489                    let from_empty = builder.build().unwrap();
490                    assert_eq!(def, from_empty);
491                )*
492            }
493        }
494    } };
495}
496
497#[cfg(test)]
498mod test {
499    // @@ begin test lint list maintained by maint/add_warning @@
500    #![allow(clippy::bool_assert_comparison)]
501    #![allow(clippy::clone_on_copy)]
502    #![allow(clippy::dbg_macro)]
503    #![allow(clippy::mixed_attributes_style)]
504    #![allow(clippy::print_stderr)]
505    #![allow(clippy::print_stdout)]
506    #![allow(clippy::single_char_pattern)]
507    #![allow(clippy::unwrap_used)]
508    #![allow(clippy::unchecked_time_subtraction)]
509    #![allow(clippy::useless_vec)]
510    #![allow(clippy::needless_pass_by_value)]
511    #![allow(clippy::string_slice)] // See arti#2571
512    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
513    use super::*;
514    use crate::{self as tor_config, sources::MustRead};
515    use derive_builder::Builder;
516    use serde::{Deserialize, Serialize};
517    use serde_json::json;
518    use tracing_test::traced_test;
519
520    #[test]
521    #[traced_test]
522    fn reconfigure_helpers() {
523        let how = Reconfigure::AllOrNothing;
524        let err = how.cannot_change("the_laws_of_physics").unwrap_err();
525        assert_eq!(
526            err.to_string(),
527            "Cannot change the_laws_of_physics on a running client.".to_owned()
528        );
529
530        let how = Reconfigure::WarnOnFailures;
531        let ok = how.cannot_change("stuff");
532        assert!(ok.is_ok());
533        assert!(logs_contain("Cannot change stuff on a running client."));
534    }
535
536    #[test]
537    #[rustfmt::skip] // autoformatting obscures the regular structure
538    fn resolve_option_test() {
539        #[derive(Debug, Clone, Builder, Eq, PartialEq)]
540        #[builder(build_fn(error = "ConfigBuildError"))]
541        #[builder(derive(Debug, Serialize, Deserialize, Eq, PartialEq))]
542        struct TestConfig {
543            #[builder(field(build = r#"tor_config::resolve_option(&self.none, || None)"#))]
544            none: Option<u32>,
545
546            #[builder(field(build = r#"tor_config::resolve_option(&self.four, || Some(4))"#))]
547            four: Option<u32>,
548        }
549
550        // defaults
551        {
552            let builder_from_json: TestConfigBuilder = serde_json::from_value(
553                json!{ { } }
554            ).unwrap();
555
556            let builder_from_methods = TestConfigBuilder::default();
557
558            assert_eq!(builder_from_methods, builder_from_json);
559            assert_eq!(builder_from_methods.build().unwrap(),
560                        TestConfig { none: None, four: Some(4) });
561        }
562
563        // explicit positive values
564        {
565            let builder_from_json: TestConfigBuilder = serde_json::from_value(
566                json!{ { "none": 123, "four": 456 } }
567            ).unwrap();
568
569            let mut builder_from_methods = TestConfigBuilder::default();
570            builder_from_methods.none(Some(123));
571            builder_from_methods.four(Some(456));
572
573            assert_eq!(builder_from_methods, builder_from_json);
574            assert_eq!(builder_from_methods.build().unwrap(),
575                       TestConfig { none: Some(123), four: Some(456) });
576        }
577
578        // explicit "null" values
579        {
580            let builder_from_json: TestConfigBuilder = serde_json::from_value(
581                json!{ { "none": 0, "four": 0 } }
582            ).unwrap();
583
584            let mut builder_from_methods = TestConfigBuilder::default();
585            builder_from_methods.none(Some(0));
586            builder_from_methods.four(Some(0));
587
588            assert_eq!(builder_from_methods, builder_from_json);
589            assert_eq!(builder_from_methods.build().unwrap(),
590                       TestConfig { none: None, four: None });
591        }
592
593        // explicit None (API only, serde can't do this for Option)
594        {
595            let mut builder_from_methods = TestConfigBuilder::default();
596            builder_from_methods.none(None);
597            builder_from_methods.four(None);
598
599            assert_eq!(builder_from_methods.build().unwrap(),
600                       TestConfig { none: None, four: None });
601        }
602    }
603
604    #[test]
605    fn get_value() {
606        use serde_value::Value as V;
607        let to_value = |json_str: &str| {
608            serde_value::to_value(serde_json::from_str::<serde_json::Value>(json_str).unwrap())
609                .unwrap()
610        };
611        let mut sources = ConfigurationSources::new_empty();
612
613        let source = "
614        [foo]
615        bar.baz = 7
616        quux = [[],[],{}]
617        ";
618        let source = ConfigurationSource::from_verbatim(source.to_string());
619        sources.push_source(source, MustRead::MustRead);
620
621        let tree = sources.load().unwrap();
622
623        {
624            let v1 = tree.get_serde_value::<V>("foo.quux").unwrap().unwrap();
625            let v2 = to_value(r#"[[], [], {}]"#);
626            assert_eq!(v1, v2);
627        }
628
629        assert!(tree.get_serde_value::<V>("nonexist").unwrap().is_none());
630        assert!(tree.get_serde_value::<V>("foo.nonexist").unwrap().is_none());
631        assert!(
632            tree.get_serde_value::<V>("foo.quux.nonexist")
633                .unwrap()
634                .is_none()
635        );
636    }
637}