Skip to main content

heddle_thread_api/
hybrid.rs

1//! HYBRID import authority is not supported yet (api#307).
2//!
3//! The api contract (`docs/alpha-v2/import-authority-host-witness.md`) says a
4//! non-HYBRID peer MUST reject any Sync record or frame carrying
5//! `import_authority` before staging, installation, relay or publication, and
6//! never silently ignore it. Presence alone rejects, including an empty bundle.
7//! Openings, ready replies and stream openings that declare HYBRID protocol
8//! support or carry a witness set are rejected the same way: this peer cannot
9//! honour them. Fields this peer sends stay empty.
10//!
11//! Each check returns the rejection message so the caller can raise it in its
12//! own protocol error type.
13use crate::contract::{
14    FetchOpen, PublicationReceipt, PublishContentOpen, ReplicationOpen, ReplicationOperations,
15    ReplicationReady, StreamOpen, TransferReady,
16};
17
18/// The reason a received message was rejected.
19pub type Rejection = &'static str;
20
21fn absent<T>(field: &Option<T>, message: Rejection) -> Result<(), Rejection> {
22    match field {
23        Some(_) => Err(message),
24        None => Ok(()),
25    }
26}
27
28pub fn operations(batch: &ReplicationOperations) -> Result<(), Rejection> {
29    absent(
30        &batch.import_authority,
31        "replication operations carry HYBRID import authority, which this peer does not support (api#307)",
32    )
33}
34
35pub fn replication_open(open: &ReplicationOpen) -> Result<(), Rejection> {
36    absent(
37        &open.protocol,
38        "replication opening declares HYBRID protocol support, which this peer does not support (api#307)",
39    )?;
40    absent(
41        &open.import_authority,
42        "replication opening carries HYBRID import authority, which this peer does not support (api#307)",
43    )
44}
45
46pub fn replication_ready(ready: &ReplicationReady) -> Result<(), Rejection> {
47    absent(
48        &ready.protocol,
49        "replication ready declares HYBRID protocol support, which this peer does not support (api#307)",
50    )?;
51    absent(
52        &ready.import_authority,
53        "replication ready carries HYBRID import authority, which this peer does not support (api#307)",
54    )
55}
56
57pub fn fetch_open(open: &FetchOpen) -> Result<(), Rejection> {
58    absent(
59        &open.protocol,
60        "fetch opening declares HYBRID protocol support, which this peer does not support (api#307)",
61    )
62}
63
64pub fn transfer_ready(ready: &TransferReady) -> Result<(), Rejection> {
65    absent(
66        &ready.protocol,
67        "transfer ready declares HYBRID protocol support, which this peer does not support (api#307)",
68    )?;
69    absent(
70        &ready.import_authority,
71        "transfer ready carries HYBRID import authority, which this peer does not support (api#307)",
72    )
73}
74
75pub fn publish_open(open: &PublishContentOpen) -> Result<(), Rejection> {
76    absent(
77        &open.protocol,
78        "publication opening declares HYBRID protocol support, which this peer does not support (api#307)",
79    )?;
80    absent(
81        &open.import_authority,
82        "publication opening carries HYBRID import authority, which this peer does not support (api#307)",
83    )
84}
85
86pub fn publication_receipt(receipt: &PublicationReceipt) -> Result<(), Rejection> {
87    absent(
88        &receipt.import_authority,
89        "publication receipt carries HYBRID import authority, which this peer does not support (api#307)",
90    )
91}
92
93pub fn stream_open(open: &StreamOpen) -> Result<(), Rejection> {
94    absent(
95        &open.protocol,
96        "stream opening declares HYBRID protocol support, which this peer does not support (api#307)",
97    )?;
98    absent(
99        &open.witness_set,
100        "stream opening carries a HYBRID witness set, which this peer does not support (api#307)",
101    )
102}
103
104#[cfg(test)]
105mod tests {
106    use api::heddle::api::common::{ProtocolCompatibility, SignedHostedWitnessSetV1};
107
108    use super::*;
109    use crate::contract::ImportPublicProofBundleV1;
110
111    fn rejected(result: Result<(), Rejection>, field: &str) {
112        let message = result.expect_err(field);
113        assert!(message.contains(field), "{message}");
114        assert!(message.contains("api#307"), "{message}");
115    }
116
117    // Presence alone rejects: an empty-but-present bundle or protocol is still
118    // a claim this peer cannot honour, so the defaults below are deliberate.
119    fn bundle() -> Option<ImportPublicProofBundleV1> {
120        Some(ImportPublicProofBundleV1::default())
121    }
122    fn protocol() -> Option<ProtocolCompatibility> {
123        Some(ProtocolCompatibility::default())
124    }
125
126    #[test]
127    fn absent_hybrid_fields_are_accepted() {
128        operations(&ReplicationOperations::default()).expect("operations");
129        replication_open(&ReplicationOpen::default()).expect("replication open");
130        replication_ready(&ReplicationReady::default()).expect("replication ready");
131        fetch_open(&FetchOpen::default()).expect("fetch open");
132        transfer_ready(&TransferReady::default()).expect("transfer ready");
133        publish_open(&PublishContentOpen::default()).expect("publish open");
134        publication_receipt(&PublicationReceipt::default()).expect("receipt");
135        stream_open(&StreamOpen::default()).expect("stream open");
136    }
137
138    #[test]
139    fn present_import_authority_is_rejected_never_ignored() {
140        rejected(
141            operations(&ReplicationOperations {
142                import_authority: bundle(),
143                ..Default::default()
144            }),
145            "import authority",
146        );
147        rejected(
148            replication_open(&ReplicationOpen {
149                import_authority: bundle(),
150                ..Default::default()
151            }),
152            "import authority",
153        );
154        rejected(
155            replication_ready(&ReplicationReady {
156                import_authority: bundle(),
157                ..Default::default()
158            }),
159            "import authority",
160        );
161        rejected(
162            transfer_ready(&TransferReady {
163                import_authority: bundle(),
164                ..Default::default()
165            }),
166            "import authority",
167        );
168        rejected(
169            publish_open(&PublishContentOpen {
170                import_authority: bundle(),
171                ..Default::default()
172            }),
173            "import authority",
174        );
175        rejected(
176            publication_receipt(&PublicationReceipt {
177                import_authority: bundle(),
178                ..Default::default()
179            }),
180            "import authority",
181        );
182    }
183
184    #[test]
185    fn declared_hybrid_protocol_and_witness_sets_are_rejected() {
186        rejected(
187            replication_open(&ReplicationOpen {
188                protocol: protocol(),
189                ..Default::default()
190            }),
191            "protocol",
192        );
193        rejected(
194            replication_ready(&ReplicationReady {
195                protocol: protocol(),
196                ..Default::default()
197            }),
198            "protocol",
199        );
200        rejected(
201            fetch_open(&FetchOpen {
202                protocol: protocol(),
203                ..Default::default()
204            }),
205            "protocol",
206        );
207        rejected(
208            transfer_ready(&TransferReady {
209                protocol: protocol(),
210                ..Default::default()
211            }),
212            "protocol",
213        );
214        rejected(
215            publish_open(&PublishContentOpen {
216                protocol: protocol(),
217                ..Default::default()
218            }),
219            "protocol",
220        );
221        rejected(
222            stream_open(&StreamOpen {
223                protocol: protocol(),
224                ..Default::default()
225            }),
226            "protocol",
227        );
228        rejected(
229            stream_open(&StreamOpen {
230                witness_set: Some(SignedHostedWitnessSetV1::default()),
231                ..Default::default()
232            }),
233            "witness set",
234        );
235    }
236}