1
//! Configuration options for types implementing [`Keystore`](crate::Keystore)
2

            
3
pub use tor_config::{ConfigBuildError, ConfigurationSource, Reconfigure};
4
pub use tor_config_path::{CfgPath, CfgPathError};
5

            
6
use amplify::Getters;
7
use derive_deftly::Deftly;
8
use serde::{Deserialize, Serialize};
9
use tor_config::derive::prelude::*;
10
use tor_config::{BoolOrAuto, ExplicitOrAuto, define_list_builder_helper, impl_not_auto_value};
11
use tor_persist::hsnickname::HsNickname;
12

            
13
use std::collections::BTreeMap;
14
use std::path::PathBuf;
15

            
16
use crate::KeystoreId;
17

            
18
/// The kind of keystore to use
19
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
20
#[serde(rename_all = "lowercase")]
21
#[non_exhaustive]
22
pub enum ArtiKeystoreKind {
23
    /// Use the [`ArtiNativeKeystore`](crate::ArtiNativeKeystore).
24
    Native,
25
    /// Use the [`ArtiEphemeralKeystore`](crate::ArtiEphemeralKeystore).
26
    #[cfg(feature = "ephemeral-keystore")]
27
    Ephemeral,
28
}
29
impl_not_auto_value! {ArtiKeystoreKind}
30

            
31
/// [`ArtiNativeKeystore`](crate::ArtiNativeKeystore) configuration
32
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
33
#[derive_deftly(TorConfig)]
34
#[deftly(tor_config(pre_build = "Self::validate"))]
35
pub struct ArtiKeystoreConfig {
36
    /// Whether keystore use is enabled.
37
    #[deftly(tor_config(default))]
38
    enabled: BoolOrAuto,
39

            
40
    /// The primary keystore.
41
    #[deftly(tor_config(sub_builder))]
42
    primary: PrimaryKeystoreConfig,
43

            
44
    /// Optionally configure C Tor keystores for arti to use.
45
    ///
46
    /// Note: The keystores listed here are read-only (keys are only
47
    /// ever written to the primary keystore, configured in
48
    /// `storage.keystore.primary`).
49
    ///
50
    /// Each C Tor keystore **must** have a unique identifier.
51
    /// It is an error to configure multiple keystores with the same [`KeystoreId`].
52
    #[deftly(tor_config(sub_builder))]
53
    ctor: CTorKeystoreConfig,
54
}
55

            
56
/// [`ArtiNativeKeystore`](crate::ArtiNativeKeystore) configuration
57
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
58
#[derive_deftly(TorConfig)]
59
#[deftly(tor_config(pre_build = "Self::validate"))]
60
pub struct CTorKeystoreConfig {
61
    /// C Tor hidden service keystores.
62
    //
63
    // NOTE: This could become a map builder, but it would change the API.
64
    #[deftly(tor_config(sub_builder))]
65
    services: CTorServiceKeystoreConfigMap,
66

            
67
    /// C Tor hidden service client keystores.
68
    //
69
    // NOTE: This could become a list builder, but it would change the API.
70
    #[deftly(tor_config(no_magic, sub_builder))]
71
    clients: CTorClientKeystoreConfigList,
72
}
73

            
74
/// Primary [`ArtiNativeKeystore`](crate::ArtiNativeKeystore) configuration
75
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize)]
76
#[derive_deftly(TorConfig)]
77
pub struct PrimaryKeystoreConfig {
78
    /// The type of keystore to use, or none at all.
79
    #[deftly(tor_config(default))]
80
    kind: ExplicitOrAuto<ArtiKeystoreKind>,
81
}
82

            
83
/// C Tor [`ArtiNativeKeystore`](crate::ArtiNativeKeystore) configuration
84
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
85
#[derive_deftly(TorConfig)]
86
#[deftly(tor_config(no_default_trait))]
87
pub struct CTorServiceKeystoreConfig {
88
    /// The identifier of this keystore.
89
    ///
90
    /// Each C Tor keystore **must**:
91
    ///
92
    ///   * have a unique identifier. It is an error to configure multiple keystores
93
    ///     with the same [`KeystoreId`].
94
    ///   * have a corresponding arti hidden service configured in the
95
    ///     `[onion_services]` section with the same nickname
96
    #[deftly(tor_config(no_default))]
97
    id: KeystoreId,
98

            
99
    /// The root directory of this keystore.
100
    ///
101
    /// This should be set to the `HiddenServiceDirectory` of your hidden service.
102
    /// Arti will read `HiddenServiceDirectory/hostname` and `HiddenServiceDirectory/private_key`.
103
    /// (Note: if your service is running in restricted discovery mode, you must also set the
104
    /// `[[onion_services."<the nickname of your svc>".restricted_discovery.key_dirs]]`
105
    /// to `HiddenServiceDirectory/client_keys`).
106
    #[deftly(tor_config(no_default))]
107
    path: PathBuf,
108

            
109
    /// The nickname of the service this keystore is to be used with.
110
    #[deftly(tor_config(no_default))]
111
    nickname: HsNickname,
112
}
113

            
114
/// Alias for a `BTreeMap` of `CTorServiceKeystoreConfig`; used to make derive_builder
115
/// happy.
116
pub(crate) type CTorServiceKeystoreConfigMap = BTreeMap<HsNickname, CTorServiceKeystoreConfig>;
117

            
118
/// The serialized format of an CTorServiceKeystoreConfigListBuilder:
119
/// a map from nickname to `CTorServiceKeystoreConfigBuilder`
120
type CTorServiceKeystoreConfigBuilderMap = BTreeMap<HsNickname, CTorServiceKeystoreConfigBuilder>;
121

            
122
define_list_builder_helper! {
123
    pub struct CTorServiceKeystoreConfigMapBuilder {
124
        stores: [CTorServiceKeystoreConfigBuilder],
125
    }
126
    built: CTorServiceKeystoreConfigMap = build_ctor_service_list(stores)?;
127
    default = vec![];
128
    #[serde(try_from="CTorServiceKeystoreConfigBuilderMap", into="CTorServiceKeystoreConfigBuilderMap")]
129
}
130

            
131
impl TryFrom<CTorServiceKeystoreConfigBuilderMap> for CTorServiceKeystoreConfigMapBuilder {
132
    type Error = ConfigBuildError;
133

            
134
832
    fn try_from(value: CTorServiceKeystoreConfigBuilderMap) -> Result<Self, Self::Error> {
135
832
        let mut list_builder = CTorServiceKeystoreConfigMapBuilder::default();
136
1664
        for (nickname, mut cfg) in value {
137
            match &cfg.nickname {
138
                Some(n) if n == &nickname => (),
139
832
                None => (),
140
                Some(other) => {
141
                    return Err(ConfigBuildError::Inconsistent {
142
                        fields: vec![nickname.to_string(), format!("{nickname}.{other}")],
143
                        problem: "mismatched nicknames on onion service.".into(),
144
                    });
145
                }
146
            }
147
832
            cfg.nickname = Some(nickname);
148
832
            list_builder.access().push(cfg);
149
        }
150
832
        Ok(list_builder)
151
832
    }
152
}
153

            
154
impl From<CTorServiceKeystoreConfigMapBuilder> for CTorServiceKeystoreConfigBuilderMap {
155
    // Note: this is *similar* to the OnionServiceProxyConfigMap implementation (it duplicates much
156
    // of that logic, so perhaps at some point it's worth abstracting all of it away behind a
157
    // general-purpose map builder API).
158
    //
159
    /// Convert our Builder representation of a set of C Tor service configs into the
160
    /// format that serde will serialize.
161
    ///
162
    /// Note: This is a potentially lossy conversion, since the serialized format
163
    /// can't represent partially-built configs without a nickname, or
164
    /// a collection of configs with duplicate nicknames.
165
4
    fn from(value: CTorServiceKeystoreConfigMapBuilder) -> CTorServiceKeystoreConfigBuilderMap {
166
4
        let mut map = BTreeMap::new();
167
4
        for cfg in value.stores.into_iter().flatten() {
168
            let nickname = cfg.nickname.clone().unwrap_or_else(|| {
169
                "Unnamed"
170
                    .to_string()
171
                    .try_into()
172
                    .expect("'Unnamed' was not a valid nickname")
173
            });
174
            map.insert(nickname, cfg);
175
        }
176
4
        map
177
4
    }
178
}
179

            
180
/// Construct a CTorServiceKeystoreConfigList from a vec of CTorServiceKeystoreConfig;
181
/// enforce that nicknames are unique.
182
///
183
/// Returns an error if the [`KeystoreId`] of the `CTorServiceKeystoreConfig`s are not unique.
184
8280
fn build_ctor_service_list(
185
8280
    ctor_stores: Vec<CTorServiceKeystoreConfig>,
186
8280
) -> Result<CTorServiceKeystoreConfigMap, ConfigBuildError> {
187
    use itertools::Itertools as _;
188

            
189
8280
    if !ctor_stores.iter().map(|s| &s.id).all_unique() {
190
        return Err(ConfigBuildError::Inconsistent {
191
            fields: ["id"].map(Into::into).into_iter().collect(),
192
            problem: "the C Tor keystores do not have unique IDs".into(),
193
        });
194
8280
    }
195

            
196
8280
    let mut map = BTreeMap::new();
197
9114
    for service in ctor_stores {
198
836
        if let Some(previous_value) = map.insert(service.nickname.clone(), service) {
199
2
            return Err(ConfigBuildError::Inconsistent {
200
2
                fields: vec!["nickname".into()],
201
2
                problem: format!(
202
2
                    "Multiple C Tor service keystores for service with nickname {}",
203
2
                    previous_value.nickname
204
2
                ),
205
2
            });
206
834
        };
207
    }
208

            
209
8278
    Ok(map)
210
8280
}
211

            
212
/// C Tor [`ArtiNativeKeystore`](crate::ArtiNativeKeystore) configuration
213
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)]
214
#[derive_deftly(TorConfig)]
215
#[deftly(tor_config(no_default_trait))]
216
pub struct CTorClientKeystoreConfig {
217
    /// The identifier of this keystore.
218
    ///
219
    /// Each keystore **must** have a unique identifier.
220
    /// It is an error to configure multiple keystores with the same [`KeystoreId`].
221
    #[deftly(tor_config(no_default))]
222
    id: KeystoreId,
223

            
224
    /// The root directory of this keystore.
225
    ///
226
    /// This should be set to the `ClientOnionAuthDir` of your client.
227
    /// If Arti is configured to run as a client (i.e. if it runs in SOCKS proxy mode),
228
    /// it will read the client restricted discovery keys from this path.
229
    ///
230
    /// The key files are expected to have the `.auth_private` extension,
231
    /// and their content **must** be of the form:
232
    /// `<56-char-onion-addr-without-.onion-part>:descriptor:x25519:<x25519 private key in base32>`.
233
    ///
234
    /// Malformed files, and files that don't have the `.auth_private` extension, will be ignored.
235
    #[deftly(tor_config(no_default))]
236
    path: PathBuf,
237
}
238

            
239
/// The serialized format of a [`CTorClientKeystoreConfigListBuilder`]:
240
pub type CTorClientKeystoreConfigList = Vec<CTorClientKeystoreConfig>;
241

            
242
define_list_builder_helper! {
243
    pub struct CTorClientKeystoreConfigListBuilder {
244
        stores: [CTorClientKeystoreConfigBuilder],
245
    }
246
    built: CTorClientKeystoreConfigList = build_ctor_client_store_config(stores)?;
247
    default = vec![];
248
}
249

            
250
/// Helper for building and validating a [`CTorClientKeystoreConfigList`].
251
///
252
/// Returns an error if the [`KeystoreId`]s of the `CTorClientKeystoreConfig`s are not unique.
253
8278
fn build_ctor_client_store_config(
254
8278
    ctor_stores: Vec<CTorClientKeystoreConfig>,
255
8278
) -> Result<CTorClientKeystoreConfigList, ConfigBuildError> {
256
    use itertools::Itertools as _;
257

            
258
8278
    if !ctor_stores.iter().map(|s| &s.id).all_unique() {
259
        return Err(ConfigBuildError::Inconsistent {
260
            fields: ["id"].map(Into::into).into_iter().collect(),
261
            problem: "the C Tor keystores do not have unique IDs".into(),
262
        });
263
8278
    }
264

            
265
8278
    Ok(ctor_stores)
266
8278
}
267

            
268
impl ArtiKeystoreConfig {
269
    /// Whether the keystore is enabled.
270
2964
    pub fn is_enabled(&self) -> bool {
271
2964
        let default = cfg!(feature = "keymgr");
272

            
273
2964
        self.enabled.as_bool().unwrap_or(default)
274
2964
    }
275

            
276
    /// The type of keystore to use
277
    ///
278
    /// Returns `None` if keystore use is disabled.
279
2964
    pub fn primary_kind(&self) -> Option<ArtiKeystoreKind> {
280
        use ExplicitOrAuto as EoA;
281

            
282
2964
        if !self.is_enabled() {
283
            return None;
284
2964
        }
285

            
286
2964
        let kind = match self.primary.kind {
287
            EoA::Explicit(kind) => kind,
288
2964
            EoA::Auto => ArtiKeystoreKind::Native,
289
        };
290

            
291
2964
        Some(kind)
292
2964
    }
293

            
294
    /// The ctor keystore configs
295
2964
    pub fn ctor_svc_stores(&self) -> impl Iterator<Item = &CTorServiceKeystoreConfig> {
296
2964
        self.ctor.services.values()
297
2964
    }
298

            
299
    /// The ctor client keystore configs
300
2964
    pub fn ctor_client_stores(&self) -> impl Iterator<Item = &CTorClientKeystoreConfig> {
301
2964
        self.ctor.clients.iter()
302
2964
    }
303
}
304

            
305
impl ArtiKeystoreConfigBuilder {
306
    /// Check that the keystore configuration is valid
307
    #[cfg(not(feature = "keymgr"))]
308
    #[allow(clippy::unnecessary_wraps)]
309
    fn validate(&self) -> Result<(), ConfigBuildError> {
310
        use BoolOrAuto as BoA;
311
        use ExplicitOrAuto as EoA;
312
        // NOTE: This could use #[deftly(tor_config(cfg))], but that would change the behavior a little.
313

            
314
        // Keystore support is disabled unless the `keymgr` feature is enabled.
315
        if self.enabled == Some(BoA::Explicit(true)) {
316
            return Err(ConfigBuildError::Inconsistent {
317
                fields: ["enabled"].map(Into::into).into_iter().collect(),
318
                problem: "keystore enabled=true, but keymgr feature not enabled".into(),
319
            });
320
        }
321

            
322
        let () = match self.primary.kind {
323
            // only enabled OR kind may be set, and when keymgr is not enabled they must be false|disabled
324
            None | Some(EoA::Auto) => Ok(()),
325
            _ => Err(ConfigBuildError::Inconsistent {
326
                fields: ["enabled", "kind"].map(Into::into).into_iter().collect(),
327
                problem: "kind!=auto, but keymgr feature not enabled".into(),
328
            }),
329
        }?;
330

            
331
        Ok(())
332
    }
333

            
334
    /// Check that the keystore configuration is valid
335
    #[cfg(feature = "keymgr")]
336
    #[allow(clippy::unnecessary_wraps)]
337
8280
    fn validate(&self) -> Result<(), ConfigBuildError> {
338
8280
        Ok(())
339
8280
    }
340

            
341
    /// Add a `CTorServiceKeystoreConfigBuilder` to this builder.
342
8
    pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
343
8
        self.ctor.ctor_service(builder);
344
8
        self
345
8
    }
346
}
347

            
348
impl CTorKeystoreConfigBuilder {
349
    /// Ensure no C Tor keystores are configured.
350
    /// (C Tor keystores are only supported if the `ctor-keystore` is enabled).
351
    #[cfg(not(feature = "ctor-keystore"))]
352
    fn validate(&self) -> Result<(), ConfigBuildError> {
353
        let no_compile_time_support = |field: &str| ConfigBuildError::NoCompileTimeSupport {
354
            field: field.into(),
355
            problem: format!("{field} configured but ctor-keystore feature not enabled"),
356
        };
357

            
358
        if self
359
            .services
360
            .stores
361
            .as_ref()
362
            .map(|s| !s.is_empty())
363
            .unwrap_or_default()
364
        {
365
            return Err(no_compile_time_support("C Tor service keystores"));
366
        }
367

            
368
        if self
369
            .clients
370
            .stores
371
            .as_ref()
372
            .map(|s| !s.is_empty())
373
            .unwrap_or_default()
374
        {
375
            return Err(no_compile_time_support("C Tor client keystores"));
376
        }
377

            
378
        Ok(())
379
    }
380

            
381
    /// Validate the configured C Tor keystores.
382
    #[cfg(feature = "ctor-keystore")]
383
8284
    fn validate(&self) -> Result<(), ConfigBuildError> {
384
        use itertools::Itertools as _;
385
        use itertools::chain;
386

            
387
8284
        let Self { services, clients } = self;
388
8284
        let mut ctor_store_ids = chain![
389
8284
            services.stores.iter().flatten().map(|s| &s.id),
390
8284
            clients.stores.iter().flatten().map(|s| &s.id)
391
        ];
392

            
393
        // This is also validated by the KeyMgrBuilder (but it's a good idea to catch this sort of
394
        // mistake at configuration-time regardless).
395
8284
        if !ctor_store_ids.all_unique() {
396
4
            return Err(ConfigBuildError::Inconsistent {
397
4
                fields: ["id"].map(Into::into).into_iter().collect(),
398
4
                problem: "the C Tor keystores do not have unique IDs".into(),
399
4
            });
400
8280
        }
401

            
402
8280
        Ok(())
403
8284
    }
404

            
405
    /// Add a `CTorServiceKeystoreConfigBuilder` to this builder.
406
8
    pub fn ctor_service(&mut self, builder: CTorServiceKeystoreConfigBuilder) -> &mut Self {
407
8
        if let Some(ref mut stores) = self.services.stores {
408
4
            stores.push(builder);
409
4
        } else {
410
4
            self.services.stores = Some(vec![builder]);
411
4
        }
412

            
413
8
        self
414
8
    }
415
}
416

            
417
#[cfg(test)]
418
mod test {
419
    // @@ begin test lint list maintained by maint/add_warning @@
420
    #![allow(clippy::bool_assert_comparison)]
421
    #![allow(clippy::clone_on_copy)]
422
    #![allow(clippy::dbg_macro)]
423
    #![allow(clippy::mixed_attributes_style)]
424
    #![allow(clippy::print_stderr)]
425
    #![allow(clippy::print_stdout)]
426
    #![allow(clippy::single_char_pattern)]
427
    #![allow(clippy::unwrap_used)]
428
    #![allow(clippy::unchecked_time_subtraction)]
429
    #![allow(clippy::useless_vec)]
430
    #![allow(clippy::needless_pass_by_value)]
431
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
432

            
433
    use super::*;
434

            
435
    use std::path::PathBuf;
436
    use std::str::FromStr as _;
437
    use tor_config::assert_config_error;
438

            
439
    /// Helper for creating [`CTorServiceKeystoreConfigBuilders`].
440
    fn svc_config_builder(
441
        id: &str,
442
        path: &str,
443
        nickname: &str,
444
    ) -> CTorServiceKeystoreConfigBuilder {
445
        let mut b = CTorServiceKeystoreConfigBuilder::default();
446
        b.id(KeystoreId::from_str(id).unwrap());
447
        b.path(PathBuf::from(path));
448
        b.nickname(HsNickname::from_str(nickname).unwrap());
449
        b
450
    }
451

            
452
    /// Helper for creating [`CTorClientKeystoreConfigBuilders`].
453
    fn client_config_builder(id: &str, path: &str) -> CTorClientKeystoreConfigBuilder {
454
        let mut b = CTorClientKeystoreConfigBuilder::default();
455
        b.id(KeystoreId::from_str(id).unwrap());
456
        b.path(PathBuf::from(path));
457
        b
458
    }
459

            
460
    #[test]
461
    #[cfg(all(feature = "ctor-keystore", feature = "keymgr"))]
462
    fn invalid_config() {
463
        let mut builder = ArtiKeystoreConfigBuilder::default();
464
        // Push two clients with the same (default) ID:
465
        builder
466
            .ctor()
467
            .clients()
468
            .access()
469
            .push(client_config_builder("foo", "/var/lib/foo"));
470

            
471
        builder
472
            .ctor()
473
            .clients()
474
            .access()
475
            .push(client_config_builder("foo", "/var/lib/bar"));
476
        let err = builder.build().unwrap_err();
477

            
478
        assert_config_error!(
479
            err,
480
            Inconsistent,
481
            "the C Tor keystores do not have unique IDs"
482
        );
483

            
484
        let mut builder = ArtiKeystoreConfigBuilder::default();
485
        // Push two services with the same ID:
486
        builder
487
            .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
488
            .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
489
        let err = builder.build().unwrap_err();
490

            
491
        assert_config_error!(
492
            err,
493
            Inconsistent,
494
            "the C Tor keystores do not have unique IDs"
495
        );
496

            
497
        let mut builder = ArtiKeystoreConfigBuilder::default();
498
        // Push two services with different IDs but same nicknames:
499
        builder
500
            .ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"))
501
            .ctor_service(svc_config_builder("bar", "/var/lib/bar", "pungent"));
502
        let err = builder.build().unwrap_err();
503

            
504
        assert_config_error!(
505
            err,
506
            Inconsistent,
507
            "Multiple C Tor service keystores for service with nickname pungent"
508
        );
509
    }
510

            
511
    #[test]
512
    #[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
513
    fn invalid_config() {
514
        let mut builder = ArtiKeystoreConfigBuilder::default();
515
        builder
516
            .ctor()
517
            .clients()
518
            .access()
519
            .push(client_config_builder("foo", "/var/lib/foo"));
520
        let err = builder.build().unwrap_err();
521

            
522
        assert_config_error!(
523
            err,
524
            NoCompileTimeSupport,
525
            "C Tor client keystores configured but ctor-keystore feature not enabled"
526
        );
527

            
528
        let mut builder = ArtiKeystoreConfigBuilder::default();
529
        builder.ctor_service(svc_config_builder("foo", "/var/lib/foo", "pungent"));
530
        let err = builder.build().unwrap_err();
531

            
532
        assert_config_error!(
533
            err,
534
            NoCompileTimeSupport,
535
            "C Tor service keystores configured but ctor-keystore feature not enabled"
536
        );
537
    }
538

            
539
    #[test]
540
    #[cfg(not(feature = "keymgr"))]
541
    fn invalid_config() {
542
        let mut builder = ArtiKeystoreConfigBuilder::default();
543
        builder.enabled(BoolOrAuto::Explicit(true));
544

            
545
        let err = builder.build().unwrap_err();
546
        assert_config_error!(
547
            err,
548
            Inconsistent,
549
            "keystore enabled=true, but keymgr feature not enabled"
550
        );
551
    }
552

            
553
    #[test]
554
    #[cfg(feature = "ctor-keystore")]
555
    fn valid_config() {
556
        let mut builder = ArtiKeystoreConfigBuilder::default();
557
        builder
558
            .ctor()
559
            .clients()
560
            .access()
561
            .push(client_config_builder("foo", "/var/lib/foo"));
562
        builder
563
            .ctor()
564
            .clients()
565
            .access()
566
            .push(client_config_builder("bar", "/var/lib/bar"));
567

            
568
        let res = builder.build();
569
        assert!(res.is_ok(), "{:?}", res);
570
    }
571

            
572
    #[test]
573
    #[cfg(all(not(feature = "ctor-keystore"), feature = "keymgr"))]
574
    fn valid_config() {
575
        let mut builder = ArtiKeystoreConfigBuilder::default();
576
        builder
577
            .enabled(BoolOrAuto::Explicit(true))
578
            .primary()
579
            .kind(ExplicitOrAuto::Explicit(ArtiKeystoreKind::Native));
580

            
581
        let res = builder.build();
582
        assert!(res.is_ok(), "{:?}", res);
583
    }
584
}