Skip to main content

derec_library/protocol/
builder.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 DeRec Alliance. All rights reserved.
3
4//! Typestate builder for [`DeRecProtocol`]. See [`DeRecProtocolBuilder`].
5//!
6//! Each store/transport slot is tracked by its own type parameter, starting
7//! at [`BuilderSlotMissingMarker`] and transitioning to
8//! [`BuilderSlotSetMarker<T>`] when its `with_*` setter runs. Setters only
9//! touch their own slot, which is what makes call order irrelevant.
10//! [`DeRecProtocolBuilder::build`] is reachable only when every slot has
11//! reached [`BuilderSlotSetMarker<_>`].
12
13use std::collections::HashMap;
14use std::time::Duration;
15
16use super::{
17    DeRecChannelStore, DeRecProtocol, DeRecSecretStore, DeRecShareStore, DeRecStateStore,
18    DeRecTransport, DeRecUserSecretStore, UnpairAck,
19};
20use derec_proto::TransportProtocol;
21
22pub struct BuilderSlotMissingMarker;
23
24pub struct BuilderSlotSetMarker<T>(T);
25
26/// Typestate builder for [`DeRecProtocol`].
27///
28/// Call each store/transport setter, then [`build`](DeRecProtocolBuilder::build).
29/// The "every required slot is filled" constraint is enforced at compile time
30/// by the impl-block bounds — calling `build()` on an incomplete builder is a
31/// type error, not a runtime panic.
32///
33/// Setters may be called in any order.
34///
35/// # Example
36///
37/// ```rust,ignore
38/// let protocol = DeRecProtocolBuilder::new()
39///     .with_channel_store(my_channel_store)
40///     .with_share_store(my_share_store)
41///     .with_secret_store(my_secret_store)
42///     .with_transport(my_transport)
43///     .with_own_transport(TransportProtocol { uri: "https://me.example.com".into(), .. })
44///     // Plus any optional with_* setters to override defaults.
45///     .build();
46/// ```
47pub struct DeRecProtocolBuilder<
48    ChannelStore,
49    ShareStore,
50    SecretStore,
51    UserSecretStore,
52    StateStore,
53    Transport,
54    OwnTransport,
55> {
56    secret_id: u64,
57    channel_store: ChannelStore,
58    share_store: ShareStore,
59    secret_store: SecretStore,
60    user_secret_store: UserSecretStore,
61    state_store: StateStore,
62    transport: Transport,
63    own_transport: OwnTransport,
64    threshold: usize,
65    keep_versions_count: usize,
66    timeout_in_secs: u64,
67    communication_info: HashMap<String, String>,
68    auto_respond_on_failure: bool,
69    unpair_ack: UnpairAck,
70    auto_reply_to: bool,
71    auto_accept: crate::protocol::AutoAcceptPolicy,
72    replica_id: Option<u64>,
73    parameter_range: Option<derec_proto::ParameterRange>,
74}
75
76impl
77    DeRecProtocolBuilder<
78        BuilderSlotMissingMarker,
79        BuilderSlotMissingMarker,
80        BuilderSlotMissingMarker,
81        BuilderSlotMissingMarker,
82        BuilderSlotMissingMarker,
83        BuilderSlotMissingMarker,
84        BuilderSlotMissingMarker,
85    >
86{
87    /// Construct a new builder bound to a specific secret.
88    ///
89    /// `secret_id` identifies the single secret this protocol instance
90    /// manages. Apps that juggle multiple secrets instantiate one
91    /// [`DeRecProtocol`] per `secret_id`.
92    pub fn new(secret_id: u64) -> Self {
93        Self {
94            secret_id,
95            channel_store: BuilderSlotMissingMarker,
96            share_store: BuilderSlotMissingMarker,
97            secret_store: BuilderSlotMissingMarker,
98            user_secret_store: BuilderSlotMissingMarker,
99            state_store: BuilderSlotMissingMarker,
100            transport: BuilderSlotMissingMarker,
101            own_transport: BuilderSlotMissingMarker,
102            threshold: 3,
103            keep_versions_count: 3,
104            timeout_in_secs: 300,
105            communication_info: HashMap::new(),
106            auto_respond_on_failure: false,
107            unpair_ack: UnpairAck::Required,
108            auto_reply_to: false,
109            auto_accept: crate::protocol::AutoAcceptPolicy::default(),
110            replica_id: None,
111            parameter_range: None,
112        }
113    }
114}
115
116impl<ChannelStore, ShareStore, SecretStore, UserSecretStore, StateStore, Transport, OwnTransport>
117    DeRecProtocolBuilder<
118        ChannelStore,
119        ShareStore,
120        SecretStore,
121        UserSecretStore,
122        StateStore,
123        Transport,
124        OwnTransport,
125    >
126{
127    /// Minimum number of shares required to reconstruct the secret.
128    ///
129    /// Default: `3`. This setter is infallible — invariant checks run
130    /// at [`build`](Self::build) time and surface a structured
131    /// [`crate::Error`] so callers can handle invalid configurations
132    /// uniformly across SDKs (FFI / WASM bindings translate that error
133    /// into their native shape rather than seeing a panic propagate
134    /// across the language boundary).
135    pub fn with_threshold(mut self, threshold: usize) -> Self {
136        self.threshold = threshold;
137        self
138    }
139
140    /// Number of recent versions each helper must retain.
141    ///
142    /// Default: `3`.
143    pub fn with_keep_versions_count(mut self, count: usize) -> Self {
144        self.keep_versions_count = count;
145        self
146    }
147
148    /// Protocol-wide staleness boundary.
149    ///
150    /// Any inbound envelope whose timestamp is older than this is discarded
151    /// on receipt, regardless of flow.
152    ///
153    /// The same threshold is also used to age out local state that is
154    /// waiting on a peer: incomplete pairings, in-flight sharing rounds,
155    /// and outstanding unpair acknowledgements.
156    ///
157    /// # Granularity
158    ///
159    /// **One second is the smallest effective unit.** The protocol's wire
160    /// timestamps (protobuf `Timestamp.seconds`) carry only whole-second
161    /// resolution, so message ages can only be measured to the nearest
162    /// second. As a consequence:
163    ///
164    /// - sub-second precision in the supplied [`Duration`] is truncated
165    ///   ([`Duration::from_millis(2500)`](Duration::from_millis) becomes 2 seconds);
166    /// - any value below one second is clamped to one second, so an
167    ///   accidental [`Duration::ZERO`] does not silently disable the timeout.
168    ///
169    /// Default: 5 minutes.
170    pub fn with_timeout(mut self, timeout: Duration) -> Self {
171        self.timeout_in_secs = timeout.as_secs().max(1);
172        self
173    }
174
175    /// Key-value pairs included in `CommunicationInfo` within pairing request
176    /// and response messages (e.g. `"name"`, `"email"`, `"phone"`).
177    ///
178    /// Default: empty.
179    pub fn with_communication_info(mut self, info: HashMap<String, String>) -> Self {
180        self.communication_info = info;
181        self
182    }
183
184    /// Whether the protocol replies to peers on inbound processing failures.
185    ///
186    /// - `true`: on a failed inbound request (e.g. format errors, decryption
187    ///   failures), the protocol automatically sends a failure response to the
188    ///   peer.
189    /// - `false`: inbound processing errors are only surfaced as events and no
190    ///   response is sent — the application decides how to respond.
191    ///
192    /// Default: `false`.
193    pub fn with_auto_respond_on_failure(mut self, enabled: bool) -> Self {
194        self.auto_respond_on_failure = enabled;
195        self
196    }
197
198    /// Whether the unpair initiator waits for the peer's acknowledgement
199    /// before dropping local state.
200    ///
201    /// - [`UnpairAck::Required`]: keep state until the peer responds with `Ok`,
202    ///   or until the timeout configured via [`Self::with_timeout`] elapses.
203    /// - [`UnpairAck::NotRequired`]: drop state immediately after sending the
204    ///   request; any later response is silently ignored.
205    ///
206    /// Default: [`UnpairAck::Required`].
207    pub fn with_unpair_ack(mut self, ack: UnpairAck) -> Self {
208        self.unpair_ack = ack;
209        self
210    }
211
212    /// Whether outbound requests carry an ephemeral `replyTo` set to this
213    /// node's own transport endpoint.
214    ///
215    /// - `true`: every outbound request envelope stamps
216    ///   `request.replyTo = own_transport`. The responder routes its
217    ///   response to that endpoint, ignoring the channel's stored peer
218    ///   endpoint. Useful when two peers share a channel record but reach
219    ///   out from different endpoints (e.g. replicas talking to a helper
220    ///   that was paired with a sibling replica) — without this, the
221    ///   responder would reply to the sibling.
222    /// - `false`: outbound requests leave `replyTo` unset. The responder
223    ///   routes to the channel's stored endpoint, which is correct for the
224    ///   single-device case.
225    ///
226    /// Only affects outbound requests originated through
227    /// [`DeRecProtocol::start`]. Responders always honour an inbound
228    /// `replyTo` regardless of this flag (it is purely a per-request hint
229    /// on the wire).
230    ///
231    /// Default: `false`.
232    pub fn with_auto_reply_to(mut self, enabled: bool) -> Self {
233        self.auto_reply_to = enabled;
234        self
235    }
236
237    /// Per-flow opt-in for auto-accepting inbound requests.
238    ///
239    /// When a flow's field on the policy is `true`,
240    /// [`DeRecProtocol::process`] internally runs the equivalent of
241    /// [`DeRecProtocol::accept`] for that flow and emits
242    /// [`crate::protocol::DeRecEvent::AutoAccepted`] in place of
243    /// [`crate::protocol::DeRecEvent::ActionRequired`] (followed in
244    /// the same event vec by the flow's completion events).
245    ///
246    /// Default: [`crate::protocol::AutoAcceptPolicy::default()`] —
247    /// every field `false`, behaviour identical to today's
248    /// `ActionRequired` flow. See the field-level docs on
249    /// [`crate::protocol::AutoAcceptPolicy`] for the per-flow trade-offs.
250    pub fn with_auto_accept(
251        mut self,
252        policy: crate::protocol::AutoAcceptPolicy,
253    ) -> Self {
254        self.auto_accept = policy;
255        self
256    }
257
258    /// Configure this node's local **replica identity**.
259    ///
260    /// Required to participate in any replica-mode pairing — when set, the
261    /// orchestrator auto-injects the id (hex-encoded) under the reserved key
262    /// `derec.replica_id` in outbound `PairRequest` / `PairResponse`
263    /// envelopes whose `sender_kind` is `ReplicaSource` or
264    /// `ReplicaDestination`, and accepts inbound replica pairings that
265    /// advertise the peer's id under the same key.
266    ///
267    /// Apps that do not use replica flows simply do not call this setter.
268    /// With no replica id configured, the orchestrator rejects every
269    /// replica-mode entry point with
270    /// [`Error::ReplicaIdNotConfigured`](crate::Error::ReplicaIdNotConfigured);
271    /// `Owner` and `Helper` pairings are unaffected.
272    ///
273    /// The id must be **stable across restarts** — persist it on the device
274    /// once and pass the same value on every protocol init. Use
275    /// [`crate::generate_replica_id`] to mint a fresh one with the OS CSPRNG.
276    ///
277    /// Default: unset (replica flows disabled).
278    pub fn with_replica_id(mut self, id: u64) -> Self {
279        self.replica_id = Some(id);
280        self
281    }
282
283    /// Declare the local node's acceptable [`ParameterRange`](derec_proto::ParameterRange)
284    /// for pair negotiation.
285    ///
286    /// Embedded in outbound `PairRequest` / `PairResponse` envelopes and
287    /// checked against the peer's range on inbound ones: if any field's
288    /// range fails to intersect (e.g. local `minShareSize` exceeds peer
289    /// `maxShareSize`) the pairing is rejected with
290    /// [`Error::Pairing(PairingError::IncompatibleParameterRange { .. })`](crate::Error::Pairing).
291    ///
292    /// Default: unset — the local side advertises no constraints and
293    /// accepts any peer range.
294    pub fn with_parameter_range(mut self, range: derec_proto::ParameterRange) -> Self {
295        self.parameter_range = Some(range);
296        self
297    }
298}
299
300impl<ShareStore, SecretStore, UserSecretStore, StateStore, Transport, OwnTransport>
301    DeRecProtocolBuilder<
302        BuilderSlotMissingMarker,
303        ShareStore,
304        SecretStore,
305        UserSecretStore,
306        StateStore,
307        Transport,
308        OwnTransport,
309    >
310{
311    /// Set the [`DeRecChannelStore`] implementation responsible for persisting
312    /// channel records.
313    pub fn with_channel_store<Cs: DeRecChannelStore>(
314        self,
315        store: Cs,
316    ) -> DeRecProtocolBuilder<
317        BuilderSlotSetMarker<Cs>,
318        ShareStore,
319        SecretStore,
320        UserSecretStore,
321        StateStore,
322        Transport,
323        OwnTransport,
324    > {
325        DeRecProtocolBuilder {
326            secret_id: self.secret_id,
327            channel_store: BuilderSlotSetMarker(store),
328            share_store: self.share_store,
329            secret_store: self.secret_store,
330            user_secret_store: self.user_secret_store,
331            state_store: self.state_store,
332            transport: self.transport,
333            own_transport: self.own_transport,
334            threshold: self.threshold,
335            keep_versions_count: self.keep_versions_count,
336            timeout_in_secs: self.timeout_in_secs,
337            communication_info: self.communication_info,
338            auto_respond_on_failure: self.auto_respond_on_failure,
339            unpair_ack: self.unpair_ack,
340            auto_reply_to: self.auto_reply_to,
341            auto_accept: self.auto_accept,
342            replica_id: self.replica_id,
343            parameter_range: self.parameter_range,
344        }
345    }
346}
347
348impl<ChannelStore, SecretStore, UserSecretStore, StateStore, Transport, OwnTransport>
349    DeRecProtocolBuilder<
350        ChannelStore,
351        BuilderSlotMissingMarker,
352        SecretStore,
353        UserSecretStore,
354        StateStore,
355        Transport,
356        OwnTransport,
357    >
358{
359    /// Set the [`DeRecShareStore`] implementation responsible for persisting
360    /// secret shares.
361    pub fn with_share_store<Sh: DeRecShareStore>(
362        self,
363        store: Sh,
364    ) -> DeRecProtocolBuilder<
365        ChannelStore,
366        BuilderSlotSetMarker<Sh>,
367        SecretStore,
368        UserSecretStore,
369        StateStore,
370        Transport,
371        OwnTransport,
372    > {
373        DeRecProtocolBuilder {
374            secret_id: self.secret_id,
375            channel_store: self.channel_store,
376            share_store: BuilderSlotSetMarker(store),
377            secret_store: self.secret_store,
378            user_secret_store: self.user_secret_store,
379            state_store: self.state_store,
380            transport: self.transport,
381            own_transport: self.own_transport,
382            threshold: self.threshold,
383            keep_versions_count: self.keep_versions_count,
384            timeout_in_secs: self.timeout_in_secs,
385            communication_info: self.communication_info,
386            auto_respond_on_failure: self.auto_respond_on_failure,
387            unpair_ack: self.unpair_ack,
388            auto_reply_to: self.auto_reply_to,
389            auto_accept: self.auto_accept,
390            replica_id: self.replica_id,
391            parameter_range: self.parameter_range,
392        }
393    }
394}
395
396impl<ChannelStore, ShareStore, UserSecretStore, StateStore, Transport, OwnTransport>
397    DeRecProtocolBuilder<
398        ChannelStore,
399        ShareStore,
400        BuilderSlotMissingMarker,
401        UserSecretStore,
402        StateStore,
403        Transport,
404        OwnTransport,
405    >
406{
407    /// Set the [`DeRecSecretStore`] implementation responsible for persisting
408    /// per-channel key material (pairing secrets, shared keys, pairing contacts).
409    pub fn with_secret_store<Ss: DeRecSecretStore>(
410        self,
411        store: Ss,
412    ) -> DeRecProtocolBuilder<
413        ChannelStore,
414        ShareStore,
415        BuilderSlotSetMarker<Ss>,
416        UserSecretStore,
417        StateStore,
418        Transport,
419        OwnTransport,
420    > {
421        DeRecProtocolBuilder {
422            secret_id: self.secret_id,
423            channel_store: self.channel_store,
424            share_store: self.share_store,
425            secret_store: BuilderSlotSetMarker(store),
426            user_secret_store: self.user_secret_store,
427            state_store: self.state_store,
428            transport: self.transport,
429            own_transport: self.own_transport,
430            threshold: self.threshold,
431            keep_versions_count: self.keep_versions_count,
432            timeout_in_secs: self.timeout_in_secs,
433            communication_info: self.communication_info,
434            auto_respond_on_failure: self.auto_respond_on_failure,
435            unpair_ack: self.unpair_ack,
436            auto_reply_to: self.auto_reply_to,
437            auto_accept: self.auto_accept,
438            replica_id: self.replica_id,
439            parameter_range: self.parameter_range,
440        }
441    }
442}
443
444impl<ChannelStore, ShareStore, SecretStore, StateStore, Transport, OwnTransport>
445    DeRecProtocolBuilder<
446        ChannelStore,
447        ShareStore,
448        SecretStore,
449        BuilderSlotMissingMarker,
450        StateStore,
451        Transport,
452        OwnTransport,
453    >
454{
455    /// Set the [`DeRecUserSecretStore`] implementation responsible for
456    /// persisting the user-facing secret contents keyed by `secret_id`.
457    /// Written on every `start(FlowKind::ProtectSecret)`; read by the
458    /// pair-completion auto-publish hook so freshly-paired peers
459    /// receive the current secret without an explicit re-publish.
460    pub fn with_user_secret_store<Us: DeRecUserSecretStore>(
461        self,
462        store: Us,
463    ) -> DeRecProtocolBuilder<
464        ChannelStore,
465        ShareStore,
466        SecretStore,
467        BuilderSlotSetMarker<Us>,
468        StateStore,
469        Transport,
470        OwnTransport,
471    > {
472        DeRecProtocolBuilder {
473            secret_id: self.secret_id,
474            channel_store: self.channel_store,
475            share_store: self.share_store,
476            secret_store: self.secret_store,
477            user_secret_store: BuilderSlotSetMarker(store),
478            state_store: self.state_store,
479            transport: self.transport,
480            own_transport: self.own_transport,
481            threshold: self.threshold,
482            keep_versions_count: self.keep_versions_count,
483            timeout_in_secs: self.timeout_in_secs,
484            communication_info: self.communication_info,
485            auto_respond_on_failure: self.auto_respond_on_failure,
486            unpair_ack: self.unpair_ack,
487            auto_reply_to: self.auto_reply_to,
488            auto_accept: self.auto_accept,
489            replica_id: self.replica_id,
490            parameter_range: self.parameter_range,
491        }
492    }
493}
494
495impl<ChannelStore, ShareStore, SecretStore, UserSecretStore, StateStore, OwnTransport>
496    DeRecProtocolBuilder<
497        ChannelStore,
498        ShareStore,
499        SecretStore,
500        UserSecretStore,
501        StateStore,
502        BuilderSlotMissingMarker,
503        OwnTransport,
504    >
505{
506    /// Set the [`DeRecTransport`] implementation responsible for delivering
507    /// outbound envelopes to peers.
508    pub fn with_transport<Tr: DeRecTransport>(
509        self,
510        transport: Tr,
511    ) -> DeRecProtocolBuilder<
512        ChannelStore,
513        ShareStore,
514        SecretStore,
515        UserSecretStore,
516        StateStore,
517        BuilderSlotSetMarker<Tr>,
518        OwnTransport,
519    > {
520        DeRecProtocolBuilder {
521            secret_id: self.secret_id,
522            channel_store: self.channel_store,
523            share_store: self.share_store,
524            secret_store: self.secret_store,
525            user_secret_store: self.user_secret_store,
526            state_store: self.state_store,
527            transport: BuilderSlotSetMarker(transport),
528            own_transport: self.own_transport,
529            threshold: self.threshold,
530            keep_versions_count: self.keep_versions_count,
531            timeout_in_secs: self.timeout_in_secs,
532            communication_info: self.communication_info,
533            auto_respond_on_failure: self.auto_respond_on_failure,
534            unpair_ack: self.unpair_ack,
535            auto_reply_to: self.auto_reply_to,
536            auto_accept: self.auto_accept,
537            replica_id: self.replica_id,
538            parameter_range: self.parameter_range,
539        }
540    }
541}
542
543impl<ChannelStore, ShareStore, SecretStore, UserSecretStore, StateStore, Transport>
544    DeRecProtocolBuilder<
545        ChannelStore,
546        ShareStore,
547        SecretStore,
548        UserSecretStore,
549        StateStore,
550        Transport,
551        BuilderSlotMissingMarker,
552    >
553{
554    /// The local node's transport endpoint that peers will use to reach it.
555    ///
556    /// Embedded into outgoing contact and pairing messages so peers know
557    /// where to send their replies. Accepts anything implementing
558    /// [`IntoOwnTransport`](crate::transport::IntoOwnTransport): a typed
559    /// [`TransportProtocol`](crate::transport::TransportProtocol), a
560    /// `&str`, or a `String`. URI validation is deferred to
561    /// [`build`](DeRecProtocolBuilder::build) so the setter chain stays
562    /// infallible — a malformed URI surfaces as
563    /// [`crate::Error::Transport`] when `build()` runs.
564    #[allow(clippy::type_complexity)]
565    pub fn with_own_transport(
566        self,
567        own_transport: impl crate::transport::IntoOwnTransport,
568    ) -> DeRecProtocolBuilder<
569        ChannelStore,
570        ShareStore,
571        SecretStore,
572        UserSecretStore,
573        StateStore,
574        Transport,
575        BuilderSlotSetMarker<
576            Result<crate::transport::TransportProtocol, crate::transport::TransportValidationError>,
577        >,
578    > {
579        let own_transport = own_transport.into_own_transport();
580        DeRecProtocolBuilder {
581            secret_id: self.secret_id,
582            channel_store: self.channel_store,
583            share_store: self.share_store,
584            secret_store: self.secret_store,
585            user_secret_store: self.user_secret_store,
586            state_store: self.state_store,
587            transport: self.transport,
588            own_transport: BuilderSlotSetMarker(own_transport),
589            threshold: self.threshold,
590            keep_versions_count: self.keep_versions_count,
591            timeout_in_secs: self.timeout_in_secs,
592            communication_info: self.communication_info,
593            auto_respond_on_failure: self.auto_respond_on_failure,
594            unpair_ack: self.unpair_ack,
595            auto_reply_to: self.auto_reply_to,
596            auto_accept: self.auto_accept,
597            replica_id: self.replica_id,
598            parameter_range: self.parameter_range,
599        }
600    }
601}
602
603impl<ChannelStore, ShareStore, SecretStore, UserSecretStore, Transport, OwnTransport>
604    DeRecProtocolBuilder<
605        ChannelStore,
606        ShareStore,
607        SecretStore,
608        UserSecretStore,
609        BuilderSlotMissingMarker,
610        Transport,
611        OwnTransport,
612    >
613{
614    /// Set the [`DeRecStateStore`] implementation responsible for
615    /// persisting in-flight orchestrator state (outstanding verification
616    /// challenges, recovery accumulators, pending unpair
617    /// acknowledgements). Required for stateless / load-balanced
618    /// deployments where the process may be recycled between an outbound
619    /// request and its inbound response; see the trait documentation for
620    /// the concurrency contract.
621    pub fn with_state_store<St: DeRecStateStore>(
622        self,
623        store: St,
624    ) -> DeRecProtocolBuilder<
625        ChannelStore,
626        ShareStore,
627        SecretStore,
628        UserSecretStore,
629        BuilderSlotSetMarker<St>,
630        Transport,
631        OwnTransport,
632    > {
633        DeRecProtocolBuilder {
634            secret_id: self.secret_id,
635            channel_store: self.channel_store,
636            share_store: self.share_store,
637            secret_store: self.secret_store,
638            user_secret_store: self.user_secret_store,
639            state_store: BuilderSlotSetMarker(store),
640            transport: self.transport,
641            own_transport: self.own_transport,
642            threshold: self.threshold,
643            keep_versions_count: self.keep_versions_count,
644            timeout_in_secs: self.timeout_in_secs,
645            communication_info: self.communication_info,
646            auto_respond_on_failure: self.auto_respond_on_failure,
647            unpair_ack: self.unpair_ack,
648            auto_reply_to: self.auto_reply_to,
649            auto_accept: self.auto_accept,
650            replica_id: self.replica_id,
651            parameter_range: self.parameter_range,
652        }
653    }
654}
655
656impl<
657    Cs: DeRecChannelStore,
658    Sh: DeRecShareStore,
659    Ss: DeRecSecretStore,
660    Us: DeRecUserSecretStore,
661    St: DeRecStateStore,
662    Tr: DeRecTransport,
663>
664    DeRecProtocolBuilder<
665        BuilderSlotSetMarker<Cs>,
666        BuilderSlotSetMarker<Sh>,
667        BuilderSlotSetMarker<Ss>,
668        BuilderSlotSetMarker<Us>,
669        BuilderSlotSetMarker<St>,
670        BuilderSlotSetMarker<Tr>,
671        BuilderSlotSetMarker<
672            Result<crate::transport::TransportProtocol, crate::transport::TransportValidationError>,
673        >,
674    >
675{
676    /// Consume the builder and return a fully-initialized [`DeRecProtocol`].
677    ///
678    /// The "all required slots set" constraint is enforced by this impl
679    /// block's type bounds — the call is only reachable once every slot
680    /// has been filled. Runtime invariant checks (currently:
681    /// `threshold >= 2` and own-transport URI validity) are deferred to
682    /// this point and surface as [`crate::Error`].
683    ///
684    /// # Errors
685    ///
686    /// - [`crate::Error::InvalidInput`] if `threshold < 2`. A threshold
687    ///   of `0` or `1` collapses threshold secret sharing and lets a
688    ///   single helper reconstruct the secret unilaterally.
689    /// - [`crate::Error::Transport`] if the URI passed to
690    ///   [`with_own_transport`](Self::with_own_transport) failed
691    ///   validation (malformed scheme, empty URI, …).
692    pub fn build(self) -> crate::Result<DeRecProtocol<Cs, Sh, Ss, Us, St, Tr>> {
693        let own_transport: TransportProtocol = self.own_transport.0?.into();
694        let mut protocol = DeRecProtocol::new(
695            self.secret_id,
696            self.channel_store.0,
697            self.share_store.0,
698            self.secret_store.0,
699            self.user_secret_store.0,
700            self.state_store.0,
701            self.transport.0,
702            own_transport,
703            self.threshold,
704            self.keep_versions_count,
705            self.timeout_in_secs,
706        )?;
707        protocol.communication_info = self.communication_info;
708        protocol.auto_respond_on_failure = self.auto_respond_on_failure;
709        protocol.unpair_ack = self.unpair_ack;
710        protocol.auto_reply_to = self.auto_reply_to;
711        protocol.auto_accept = self.auto_accept;
712        protocol.replica_id = self.replica_id;
713        protocol.parameter_range = self.parameter_range;
714        Ok(protocol)
715    }
716}
717
718#[cfg(test)]
719mod tests {
720    use super::*;
721
722    /// Boundary value: threshold == 2 is the minimum valid input.
723    #[test]
724    fn with_threshold_accepts_2() {
725        let b = DeRecProtocolBuilder::new(0).with_threshold(2);
726        assert_eq!(b.threshold, 2);
727    }
728
729    /// Builder round-trip: `with_auto_accept` stores the policy on the
730    /// builder so it lands on the eventual `DeRecProtocol`.
731    #[test]
732    fn with_auto_accept_round_trips_policy() {
733        let policy = crate::protocol::AutoAcceptPolicy {
734            store_share: true,
735            verify_share: true,
736            ..Default::default()
737        };
738        let b = DeRecProtocolBuilder::new(0).with_auto_accept(policy);
739        assert_eq!(b.auto_accept, policy);
740    }
741
742    /// Default builder leaves `auto_accept` empty (every flow off).
743    #[test]
744    fn auto_accept_defaults_to_empty_policy() {
745        let b = DeRecProtocolBuilder::new(0);
746        assert_eq!(b.auto_accept, crate::protocol::AutoAcceptPolicy::default());
747    }
748
749    /// Higher thresholds (production default and beyond) pass through.
750    #[test]
751    fn with_threshold_accepts_3_and_above() {
752        let b3 = DeRecProtocolBuilder::new(0).with_threshold(3);
753        assert_eq!(b3.threshold, 3);
754        let b_high = DeRecProtocolBuilder::new(0).with_threshold(100);
755        assert_eq!(b_high.threshold, 100);
756    }
757
758    /// `with_threshold` is infallible — invalid values are accepted
759    /// here and surface as `Error::InvalidInput` at `build()` time.
760    /// This test only asserts the value round-trips into the builder.
761    #[test]
762    fn with_threshold_accepts_invalid_values_silently() {
763        let b0 = DeRecProtocolBuilder::new(0).with_threshold(0);
764        assert_eq!(b0.threshold, 0);
765        let b1 = DeRecProtocolBuilder::new(0).with_threshold(1);
766        assert_eq!(b1.threshold, 1);
767    }
768
769    /// The low-level [`DeRecProtocol::new`] constructor enforces the
770    /// threshold floor for callers that bypass the typed builder. We
771    /// construct via no-op stores so the type bound resolves with
772    /// concrete `DeRecChannelStore` etc. implementations.
773    #[test]
774    fn protocol_new_rejects_zero_threshold() {
775        use crate::protocol::traits::{
776            ChannelStoreFuture, DeRecChannelStore, DeRecSecretStore, DeRecShareStore,
777            DeRecTransport, DeRecUserSecretStore, SecretStoreFuture, ShareStoreFuture,
778            TransportFuture,
779        };
780        use crate::protocol::types::{Channel, MissingPolicy, SecretKind, SecretValue, Share, UserSecrets};
781        use crate::types::ChannelId;
782        use derec_proto::TransportProtocol;
783
784        struct NoopChannelStore;
785        impl DeRecChannelStore for NoopChannelStore {
786            fn load(&self, _: u64, _: ChannelId) -> ChannelStoreFuture<'_, Option<Channel>> {
787                Box::pin(std::future::ready(Ok(None)))
788            }
789            fn save(&mut self, _: u64, _: Channel) -> ChannelStoreFuture<'_, ()> {
790                Box::pin(std::future::ready(Ok(())))
791            }
792            fn remove(&mut self, _: u64, _: ChannelId) -> ChannelStoreFuture<'_, bool> {
793                Box::pin(std::future::ready(Ok(false)))
794            }
795            fn channels(&self, _: u64) -> ChannelStoreFuture<'_, Vec<Channel>> {
796                Box::pin(std::future::ready(Ok(Vec::new())))
797            }
798            fn link_channel(
799                &mut self,
800                _: u64,
801                _: ChannelId,
802                _: ChannelId,
803            ) -> ChannelStoreFuture<'_, ()> {
804                Box::pin(std::future::ready(Ok(())))
805            }
806            fn linked_channels(
807                &self,
808                _: u64,
809                cid: ChannelId,
810            ) -> ChannelStoreFuture<'_, Vec<ChannelId>> {
811                Box::pin(std::future::ready(Ok(vec![cid])))
812            }
813        }
814
815        struct NoopShareStore;
816        impl DeRecShareStore for NoopShareStore {
817            fn load(
818                &self,
819                _: u64,
820                _: ChannelId,
821                _: &[u32],
822            ) -> ShareStoreFuture<'_, Vec<Share>> {
823                Box::pin(std::future::ready(Ok(Vec::new())))
824            }
825            fn load_many(
826                &self,
827                _: u64,
828                _: &[ChannelId],
829                _: &[u32],
830            ) -> ShareStoreFuture<'_, Vec<Share>> {
831                Box::pin(std::future::ready(Ok(Vec::new())))
832            }
833            fn load_all(
834                &self,
835                _: u64,
836                _: &[ChannelId],
837            ) -> ShareStoreFuture<'_, Vec<Share>> {
838                Box::pin(std::future::ready(Ok(Vec::new())))
839            }
840            fn latest_version(&self, _: u64) -> ShareStoreFuture<'_, Option<u32>> {
841                Box::pin(std::future::ready(Ok(None)))
842            }
843            fn save(&mut self, _: u64, _: ChannelId, _: Share) -> ShareStoreFuture<'_, ()> {
844                Box::pin(std::future::ready(Ok(())))
845            }
846            fn remove_channel(&mut self, _: u64, _: ChannelId) -> ShareStoreFuture<'_, ()> {
847                Box::pin(std::future::ready(Ok(())))
848            }
849        }
850
851        struct NoopSecretStore;
852        impl DeRecSecretStore for NoopSecretStore {
853            fn load(
854                &self,
855                _: u64,
856                _: ChannelId,
857                _: SecretKind,
858            ) -> SecretStoreFuture<'_, Option<SecretValue>> {
859                Box::pin(std::future::ready(Ok(None)))
860            }
861            fn load_many(
862                &self,
863                _: u64,
864                _: &[ChannelId],
865                _: SecretKind,
866                _: MissingPolicy,
867            ) -> SecretStoreFuture<'_, Vec<(ChannelId, SecretValue)>> {
868                Box::pin(std::future::ready(Ok(Vec::new())))
869            }
870            fn save(
871                &mut self,
872                _: u64,
873                _: ChannelId,
874                _: SecretValue,
875            ) -> SecretStoreFuture<'_, ()> {
876                Box::pin(std::future::ready(Ok(())))
877            }
878            fn remove(
879                &mut self,
880                _: u64,
881                _: ChannelId,
882                _: SecretKind,
883            ) -> SecretStoreFuture<'_, ()> {
884                Box::pin(std::future::ready(Ok(())))
885            }
886        }
887
888        struct NoopUserSecretStore;
889        impl DeRecUserSecretStore for NoopUserSecretStore {
890            fn load_latest(&self, _: u64) -> ShareStoreFuture<'_, Option<UserSecrets>> {
891                Box::pin(std::future::ready(Ok(None)))
892            }
893            fn save_latest(&mut self, _: u64, _: UserSecrets) -> ShareStoreFuture<'_, ()> {
894                Box::pin(std::future::ready(Ok(())))
895            }
896            fn remove(&mut self, _: u64) -> ShareStoreFuture<'_, ()> {
897                Box::pin(std::future::ready(Ok(())))
898            }
899        }
900
901        struct NoopTransport;
902        impl DeRecTransport for NoopTransport {
903            fn send(&self, _: &TransportProtocol, _: Vec<u8>) -> TransportFuture<'_> {
904                Box::pin(std::future::ready(Ok(())))
905            }
906        }
907
908        struct NoopStateStore;
909        impl crate::protocol::DeRecStateStore for NoopStateStore {
910            fn save(
911                &mut self,
912                _: u64,
913                _: crate::protocol::StateItem,
914            ) -> crate::protocol::StateStoreFuture<'_, ()> {
915                Box::pin(std::future::ready(Ok(())))
916            }
917            fn load(
918                &self,
919                _: u64,
920                _: crate::protocol::StateKey,
921            ) -> crate::protocol::StateStoreFuture<'_, Option<crate::protocol::StateItem>> {
922                Box::pin(std::future::ready(Ok(None)))
923            }
924            fn remove(
925                &mut self,
926                _: u64,
927                _: crate::protocol::StateKey,
928            ) -> crate::protocol::StateStoreFuture<'_, bool> {
929                Box::pin(std::future::ready(Ok(false)))
930            }
931            fn load_all(
932                &self,
933                _: u64,
934                _: crate::protocol::StateKind,
935            ) -> crate::protocol::StateStoreFuture<'_, Vec<crate::protocol::StateItem>> {
936                Box::pin(std::future::ready(Ok(Vec::new())))
937            }
938        }
939
940        // threshold = 0 — `DeRecProtocol::new` returns
941        // `Error::InvalidInput` rather than panicking.
942        let result = DeRecProtocol::new(
943            0,
944            NoopChannelStore,
945            NoopShareStore,
946            NoopSecretStore,
947            NoopUserSecretStore,
948            NoopStateStore,
949            NoopTransport,
950            TransportProtocol {
951                uri: String::new(),
952                protocol: 0,
953            },
954            0, // ← invalid threshold
955            3,
956            30,
957        );
958        assert!(matches!(result, Err(crate::Error::InvalidInput(_))));
959    }
960
961    /// End-to-end: the typed builder propagates the threshold error
962    /// from `DeRecProtocol::new` instead of panicking, so callers can
963    /// handle invalid configurations uniformly via `Result`.
964    #[test]
965    fn build_rejects_zero_threshold_via_invalid_input() {
966        use crate::protocol::traits::{
967            ChannelStoreFuture, DeRecChannelStore, DeRecSecretStore, DeRecShareStore,
968            DeRecTransport, DeRecUserSecretStore, SecretStoreFuture, ShareStoreFuture,
969            TransportFuture,
970        };
971        use crate::protocol::types::{
972            Channel, MissingPolicy, SecretKind, SecretValue, Share, UserSecrets,
973        };
974        use crate::types::ChannelId;
975        use derec_proto::TransportProtocol;
976
977        struct NoopChannelStore;
978        impl DeRecChannelStore for NoopChannelStore {
979            fn load(&self, _: u64, _: ChannelId) -> ChannelStoreFuture<'_, Option<Channel>> {
980                Box::pin(std::future::ready(Ok(None)))
981            }
982            fn save(&mut self, _: u64, _: Channel) -> ChannelStoreFuture<'_, ()> {
983                Box::pin(std::future::ready(Ok(())))
984            }
985            fn remove(&mut self, _: u64, _: ChannelId) -> ChannelStoreFuture<'_, bool> {
986                Box::pin(std::future::ready(Ok(false)))
987            }
988            fn channels(&self, _: u64) -> ChannelStoreFuture<'_, Vec<Channel>> {
989                Box::pin(std::future::ready(Ok(Vec::new())))
990            }
991            fn link_channel(
992                &mut self,
993                _: u64,
994                _: ChannelId,
995                _: ChannelId,
996            ) -> ChannelStoreFuture<'_, ()> {
997                Box::pin(std::future::ready(Ok(())))
998            }
999            fn linked_channels(
1000                &self,
1001                _: u64,
1002                cid: ChannelId,
1003            ) -> ChannelStoreFuture<'_, Vec<ChannelId>> {
1004                Box::pin(std::future::ready(Ok(vec![cid])))
1005            }
1006        }
1007        struct NoopShareStore;
1008        impl DeRecShareStore for NoopShareStore {
1009            fn load(
1010                &self,
1011                _: u64,
1012                _: ChannelId,
1013                _: &[u32],
1014            ) -> ShareStoreFuture<'_, Vec<Share>> {
1015                Box::pin(std::future::ready(Ok(Vec::new())))
1016            }
1017            fn load_many(
1018                &self,
1019                _: u64,
1020                _: &[ChannelId],
1021                _: &[u32],
1022            ) -> ShareStoreFuture<'_, Vec<Share>> {
1023                Box::pin(std::future::ready(Ok(Vec::new())))
1024            }
1025            fn load_all(
1026                &self,
1027                _: u64,
1028                _: &[ChannelId],
1029            ) -> ShareStoreFuture<'_, Vec<Share>> {
1030                Box::pin(std::future::ready(Ok(Vec::new())))
1031            }
1032            fn latest_version(&self, _: u64) -> ShareStoreFuture<'_, Option<u32>> {
1033                Box::pin(std::future::ready(Ok(None)))
1034            }
1035            fn save(&mut self, _: u64, _: ChannelId, _: Share) -> ShareStoreFuture<'_, ()> {
1036                Box::pin(std::future::ready(Ok(())))
1037            }
1038            fn remove_channel(&mut self, _: u64, _: ChannelId) -> ShareStoreFuture<'_, ()> {
1039                Box::pin(std::future::ready(Ok(())))
1040            }
1041        }
1042        struct NoopSecretStore;
1043        impl DeRecSecretStore for NoopSecretStore {
1044            fn load(
1045                &self,
1046                _: u64,
1047                _: ChannelId,
1048                _: SecretKind,
1049            ) -> SecretStoreFuture<'_, Option<SecretValue>> {
1050                Box::pin(std::future::ready(Ok(None)))
1051            }
1052            fn load_many(
1053                &self,
1054                _: u64,
1055                _: &[ChannelId],
1056                _: SecretKind,
1057                _: MissingPolicy,
1058            ) -> SecretStoreFuture<'_, Vec<(ChannelId, SecretValue)>> {
1059                Box::pin(std::future::ready(Ok(Vec::new())))
1060            }
1061            fn save(
1062                &mut self,
1063                _: u64,
1064                _: ChannelId,
1065                _: SecretValue,
1066            ) -> SecretStoreFuture<'_, ()> {
1067                Box::pin(std::future::ready(Ok(())))
1068            }
1069            fn remove(
1070                &mut self,
1071                _: u64,
1072                _: ChannelId,
1073                _: SecretKind,
1074            ) -> SecretStoreFuture<'_, ()> {
1075                Box::pin(std::future::ready(Ok(())))
1076            }
1077        }
1078        struct NoopUserSecretStore;
1079        impl DeRecUserSecretStore for NoopUserSecretStore {
1080            fn load_latest(&self, _: u64) -> ShareStoreFuture<'_, Option<UserSecrets>> {
1081                Box::pin(std::future::ready(Ok(None)))
1082            }
1083            fn save_latest(&mut self, _: u64, _: UserSecrets) -> ShareStoreFuture<'_, ()> {
1084                Box::pin(std::future::ready(Ok(())))
1085            }
1086            fn remove(&mut self, _: u64) -> ShareStoreFuture<'_, ()> {
1087                Box::pin(std::future::ready(Ok(())))
1088            }
1089        }
1090        struct NoopTransport;
1091        impl DeRecTransport for NoopTransport {
1092            fn send(&self, _: &TransportProtocol, _: Vec<u8>) -> TransportFuture<'_> {
1093                Box::pin(std::future::ready(Ok(())))
1094            }
1095        }
1096
1097        struct NoopStateStore;
1098        impl crate::protocol::DeRecStateStore for NoopStateStore {
1099            fn save(
1100                &mut self,
1101                _: u64,
1102                _: crate::protocol::StateItem,
1103            ) -> crate::protocol::StateStoreFuture<'_, ()> {
1104                Box::pin(std::future::ready(Ok(())))
1105            }
1106            fn load(
1107                &self,
1108                _: u64,
1109                _: crate::protocol::StateKey,
1110            ) -> crate::protocol::StateStoreFuture<'_, Option<crate::protocol::StateItem>> {
1111                Box::pin(std::future::ready(Ok(None)))
1112            }
1113            fn remove(
1114                &mut self,
1115                _: u64,
1116                _: crate::protocol::StateKey,
1117            ) -> crate::protocol::StateStoreFuture<'_, bool> {
1118                Box::pin(std::future::ready(Ok(false)))
1119            }
1120            fn load_all(
1121                &self,
1122                _: u64,
1123                _: crate::protocol::StateKind,
1124            ) -> crate::protocol::StateStoreFuture<'_, Vec<crate::protocol::StateItem>> {
1125                Box::pin(std::future::ready(Ok(Vec::new())))
1126            }
1127        }
1128
1129        let result = DeRecProtocolBuilder::new(0)
1130            .with_channel_store(NoopChannelStore)
1131            .with_share_store(NoopShareStore)
1132            .with_secret_store(NoopSecretStore)
1133            .with_user_secret_store(NoopUserSecretStore)
1134            .with_transport(NoopTransport)
1135            .with_state_store(NoopStateStore)
1136            .with_own_transport("https://owner.example/derec")
1137            .with_threshold(1)
1138            .build();
1139        assert!(matches!(result, Err(crate::Error::InvalidInput(_))));
1140    }
1141
1142    /// `with_own_transport` defers URI validation to `build()`, so a
1143    /// malformed scheme surfaces as `crate::Error::Transport` rather
1144    /// than panicking mid-chain or being silently accepted.
1145    #[test]
1146    fn build_rejects_malformed_own_transport_via_transport_error() {
1147        use crate::protocol::traits::{
1148            ChannelStoreFuture, DeRecChannelStore, DeRecSecretStore, DeRecShareStore,
1149            DeRecTransport, DeRecUserSecretStore, SecretStoreFuture, ShareStoreFuture,
1150            TransportFuture,
1151        };
1152        use crate::protocol::types::{
1153            Channel, MissingPolicy, SecretKind, SecretValue, Share, UserSecrets,
1154        };
1155        use crate::types::ChannelId;
1156        use derec_proto::TransportProtocol;
1157
1158        struct NoopChannelStore;
1159        impl DeRecChannelStore for NoopChannelStore {
1160            fn load(&self, _: u64, _: ChannelId) -> ChannelStoreFuture<'_, Option<Channel>> {
1161                Box::pin(std::future::ready(Ok(None)))
1162            }
1163            fn save(&mut self, _: u64, _: Channel) -> ChannelStoreFuture<'_, ()> {
1164                Box::pin(std::future::ready(Ok(())))
1165            }
1166            fn remove(&mut self, _: u64, _: ChannelId) -> ChannelStoreFuture<'_, bool> {
1167                Box::pin(std::future::ready(Ok(false)))
1168            }
1169            fn channels(&self, _: u64) -> ChannelStoreFuture<'_, Vec<Channel>> {
1170                Box::pin(std::future::ready(Ok(Vec::new())))
1171            }
1172            fn link_channel(
1173                &mut self,
1174                _: u64,
1175                _: ChannelId,
1176                _: ChannelId,
1177            ) -> ChannelStoreFuture<'_, ()> {
1178                Box::pin(std::future::ready(Ok(())))
1179            }
1180            fn linked_channels(
1181                &self,
1182                _: u64,
1183                cid: ChannelId,
1184            ) -> ChannelStoreFuture<'_, Vec<ChannelId>> {
1185                Box::pin(std::future::ready(Ok(vec![cid])))
1186            }
1187        }
1188        struct NoopShareStore;
1189        impl DeRecShareStore for NoopShareStore {
1190            fn load(
1191                &self,
1192                _: u64,
1193                _: ChannelId,
1194                _: &[u32],
1195            ) -> ShareStoreFuture<'_, Vec<Share>> {
1196                Box::pin(std::future::ready(Ok(Vec::new())))
1197            }
1198            fn load_many(
1199                &self,
1200                _: u64,
1201                _: &[ChannelId],
1202                _: &[u32],
1203            ) -> ShareStoreFuture<'_, Vec<Share>> {
1204                Box::pin(std::future::ready(Ok(Vec::new())))
1205            }
1206            fn load_all(
1207                &self,
1208                _: u64,
1209                _: &[ChannelId],
1210            ) -> ShareStoreFuture<'_, Vec<Share>> {
1211                Box::pin(std::future::ready(Ok(Vec::new())))
1212            }
1213            fn latest_version(&self, _: u64) -> ShareStoreFuture<'_, Option<u32>> {
1214                Box::pin(std::future::ready(Ok(None)))
1215            }
1216            fn save(&mut self, _: u64, _: ChannelId, _: Share) -> ShareStoreFuture<'_, ()> {
1217                Box::pin(std::future::ready(Ok(())))
1218            }
1219            fn remove_channel(&mut self, _: u64, _: ChannelId) -> ShareStoreFuture<'_, ()> {
1220                Box::pin(std::future::ready(Ok(())))
1221            }
1222        }
1223        struct NoopSecretStore;
1224        impl DeRecSecretStore for NoopSecretStore {
1225            fn load(
1226                &self,
1227                _: u64,
1228                _: ChannelId,
1229                _: SecretKind,
1230            ) -> SecretStoreFuture<'_, Option<SecretValue>> {
1231                Box::pin(std::future::ready(Ok(None)))
1232            }
1233            fn load_many(
1234                &self,
1235                _: u64,
1236                _: &[ChannelId],
1237                _: SecretKind,
1238                _: MissingPolicy,
1239            ) -> SecretStoreFuture<'_, Vec<(ChannelId, SecretValue)>> {
1240                Box::pin(std::future::ready(Ok(Vec::new())))
1241            }
1242            fn save(
1243                &mut self,
1244                _: u64,
1245                _: ChannelId,
1246                _: SecretValue,
1247            ) -> SecretStoreFuture<'_, ()> {
1248                Box::pin(std::future::ready(Ok(())))
1249            }
1250            fn remove(
1251                &mut self,
1252                _: u64,
1253                _: ChannelId,
1254                _: SecretKind,
1255            ) -> SecretStoreFuture<'_, ()> {
1256                Box::pin(std::future::ready(Ok(())))
1257            }
1258        }
1259        struct NoopUserSecretStore;
1260        impl DeRecUserSecretStore for NoopUserSecretStore {
1261            fn load_latest(&self, _: u64) -> ShareStoreFuture<'_, Option<UserSecrets>> {
1262                Box::pin(std::future::ready(Ok(None)))
1263            }
1264            fn save_latest(&mut self, _: u64, _: UserSecrets) -> ShareStoreFuture<'_, ()> {
1265                Box::pin(std::future::ready(Ok(())))
1266            }
1267            fn remove(&mut self, _: u64) -> ShareStoreFuture<'_, ()> {
1268                Box::pin(std::future::ready(Ok(())))
1269            }
1270        }
1271        struct NoopTransport;
1272        impl DeRecTransport for NoopTransport {
1273            fn send(&self, _: &TransportProtocol, _: Vec<u8>) -> TransportFuture<'_> {
1274                Box::pin(std::future::ready(Ok(())))
1275            }
1276        }
1277
1278        struct NoopStateStore;
1279        impl crate::protocol::DeRecStateStore for NoopStateStore {
1280            fn save(
1281                &mut self,
1282                _: u64,
1283                _: crate::protocol::StateItem,
1284            ) -> crate::protocol::StateStoreFuture<'_, ()> {
1285                Box::pin(std::future::ready(Ok(())))
1286            }
1287            fn load(
1288                &self,
1289                _: u64,
1290                _: crate::protocol::StateKey,
1291            ) -> crate::protocol::StateStoreFuture<'_, Option<crate::protocol::StateItem>> {
1292                Box::pin(std::future::ready(Ok(None)))
1293            }
1294            fn remove(
1295                &mut self,
1296                _: u64,
1297                _: crate::protocol::StateKey,
1298            ) -> crate::protocol::StateStoreFuture<'_, bool> {
1299                Box::pin(std::future::ready(Ok(false)))
1300            }
1301            fn load_all(
1302                &self,
1303                _: u64,
1304                _: crate::protocol::StateKind,
1305            ) -> crate::protocol::StateStoreFuture<'_, Vec<crate::protocol::StateItem>> {
1306                Box::pin(std::future::ready(Ok(Vec::new())))
1307            }
1308        }
1309
1310        let result = DeRecProtocolBuilder::new(0)
1311            .with_channel_store(NoopChannelStore)
1312            .with_share_store(NoopShareStore)
1313            .with_secret_store(NoopSecretStore)
1314            .with_user_secret_store(NoopUserSecretStore)
1315            .with_transport(NoopTransport)
1316            .with_state_store(NoopStateStore)
1317            .with_own_transport("ws://owner.example/derec")
1318            .with_threshold(2)
1319            .build();
1320        assert!(matches!(
1321            result,
1322            Err(crate::Error::Transport(
1323                crate::transport::TransportValidationError::SchemeMismatch { .. }
1324            ))
1325        ));
1326    }
1327}