Skip to main content

rain_metadata/meta/
mod.rs

1use super::error::Error;
2
3pub mod cache;
4use cache::MetaCache;
5use alloy::primitives::{hex, keccak256};
6use futures::future;
7use graphql_client::GraphQLQuery;
8use rain_metadata_bindings::IDescribedByMetaV1;
9use reqwest::Client;
10use serde::de::{Deserialize, Deserializer, Visitor};
11use serde::ser::{Serialize, SerializeMap, Serializer};
12use std::{
13    collections::{BTreeSet, HashMap},
14    convert::TryFrom,
15    fmt::Debug,
16    sync::Arc,
17};
18use strum::{EnumIter, EnumString};
19use alloy::sol_types::private::Address;
20use alloy::providers::Provider;
21use alloy::contract::Error as ContractError;
22use rain_erc::erc165::{Erc165Error, IERC165, XorSelectors, supports_erc165};
23
24pub mod magic;
25pub(crate) mod normalize;
26pub(crate) mod query;
27pub mod types;
28
29pub use magic::*;
30pub use query::*;
31
32/// All known meta identifiers
33#[derive(Copy, Clone, EnumString, EnumIter, strum::Display, Debug, PartialEq)]
34#[strum(serialize_all = "kebab-case")]
35pub enum KnownMeta {
36    /// Ops meta v1. Still a known meta - the magic number is in the
37    /// metadata-v1 table and an item can legitimately carry it - but this
38    /// crate no longer models or validates the payload. The interpreter
39    /// describes its words as AuthoringMetaV2 now (`LibAllStandardOps`
40    /// publishes exactly that), and the only surviving op meta references in
41    /// the org are deprecated IExpressionDeployer interfaces.
42    OpV1,
43    DotrainV1,
44    RainlangV1,
45    SolidityAbiV2,
46    AuthoringMetaV1,
47    AuthoringMetaV2,
48    InterpreterCallerMetaV1,
49    ExpressionDeployerV2BytecodeV1,
50    RainlangSourceV1,
51    AddressList,
52    DotrainSourceV1,
53    OrderBuilderStateV1,
54    RaindexSignedContextOracleV1,
55}
56
57impl TryFrom<KnownMagic> for KnownMeta {
58    type Error = Error;
59    fn try_from(value: KnownMagic) -> Result<Self, Self::Error> {
60        match value {
61            KnownMagic::DotrainV1 => Ok(KnownMeta::DotrainV1),
62            KnownMagic::RainlangV1 => Ok(KnownMeta::RainlangV1),
63            KnownMagic::SolidityAbiV2 => Ok(KnownMeta::SolidityAbiV2),
64            KnownMagic::OpMetaV1 => Ok(KnownMeta::OpV1),
65            KnownMagic::AuthoringMetaV1 => Ok(KnownMeta::AuthoringMetaV1),
66            KnownMagic::AuthoringMetaV2 => Ok(KnownMeta::AuthoringMetaV2),
67            KnownMagic::AddressList => Ok(KnownMeta::AddressList),
68            KnownMagic::InterpreterCallerMetaV1 => Ok(KnownMeta::InterpreterCallerMetaV1),
69            KnownMagic::DotrainSourceV1 => Ok(KnownMeta::DotrainSourceV1),
70            KnownMagic::OrderBuilderStateV1 => Ok(KnownMeta::OrderBuilderStateV1),
71            KnownMagic::ExpressionDeployerV2BytecodeV1 => {
72                Ok(KnownMeta::ExpressionDeployerV2BytecodeV1)
73            }
74            KnownMagic::RainlangSourceV1 => Ok(KnownMeta::RainlangSourceV1),
75            KnownMagic::RaindexSignedContextOracleV1 => Ok(KnownMeta::RaindexSignedContextOracleV1),
76            _ => Err(Error::UnsupportedMeta),
77        }
78    }
79}
80
81/// Content type of a cbor meta map
82#[derive(
83    Copy,
84    Clone,
85    Debug,
86    EnumIter,
87    PartialEq,
88    EnumString,
89    strum::Display,
90    serde::Serialize,
91    serde::Deserialize,
92)]
93#[strum(serialize_all = "kebab-case")]
94pub enum ContentType {
95    None,
96    #[serde(rename = "application/json")]
97    Json,
98    #[serde(rename = "application/cbor")]
99    Cbor,
100    #[serde(rename = "application/octet-stream")]
101    OctetStream,
102}
103
104/// Content encoding of a cbor meta map
105#[derive(
106    Copy,
107    Clone,
108    Debug,
109    EnumIter,
110    PartialEq,
111    EnumString,
112    strum::Display,
113    serde::Serialize,
114    serde::Deserialize,
115)]
116#[serde(rename_all = "kebab-case")]
117#[strum(serialize_all = "kebab-case")]
118pub enum ContentEncoding {
119    None,
120    Identity,
121    Deflate,
122}
123
124impl ContentEncoding {
125    /// encode the data based on the variant
126    pub fn encode(&self, data: &[u8]) -> Vec<u8> {
127        match self {
128            ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
129            ContentEncoding::Deflate => deflate::deflate_bytes_zlib(data),
130        }
131    }
132
133    /// decode the data based on the variant
134    pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
135        Ok(match self {
136            ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
137            ContentEncoding::Deflate => match inflate::inflate_bytes_zlib(data) {
138                Ok(v) => v,
139                Err(error) => match inflate::inflate_bytes(data) {
140                    Ok(v) => v,
141                    Err(_) => Err(Error::InflateError(error))?,
142                },
143            },
144        })
145    }
146}
147
148/// Content language of a cbor meta map
149#[derive(
150    Copy,
151    Clone,
152    Debug,
153    EnumIter,
154    PartialEq,
155    EnumString,
156    strum::Display,
157    serde::Serialize,
158    serde::Deserialize,
159)]
160#[serde(rename_all = "kebab-case")]
161#[strum(serialize_all = "kebab-case")]
162pub enum ContentLanguage {
163    None,
164    En,
165}
166
167/// # Rain Meta Document v1 Item (meta map)
168///
169/// represents a rain meta data and configuration that can be cbor encoded or unpacked back to the meta types
170#[derive(PartialEq, Debug, Clone)]
171pub struct RainMetaDocumentV1Item {
172    pub payload: serde_bytes::ByteBuf,
173    pub magic: KnownMagic,
174    pub content_type: ContentType,
175    pub content_encoding: ContentEncoding,
176    pub content_language: ContentLanguage,
177    /// optional reference to the schema of the payload, encoded under the
178    /// [KnownMagic::OaSchema] magic number as an additional cbor map key
179    /// beyond the standard 0-4 keys
180    pub schema: Option<String>,
181}
182
183// this implementation is mainly used by Rainlang and Dotrain metas as they are aliased type for String
184impl TryFrom<RainMetaDocumentV1Item> for String {
185    type Error = Error;
186    fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
187        Ok(String::from_utf8(value.unpack()?)?)
188    }
189}
190
191// this implementation is mainly used by ExpressionDeployerV2Bytecode meta as it is aliased type for Vec<u8>
192impl TryFrom<RainMetaDocumentV1Item> for Vec<u8> {
193    type Error = Error;
194    fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
195        value.unpack()
196    }
197}
198
199impl RainMetaDocumentV1Item {
200    fn len(&self) -> usize {
201        let mut l = 2;
202        if !matches!(self.content_type, ContentType::None) {
203            l += 1;
204        }
205        if !matches!(self.content_encoding, ContentEncoding::None) {
206            l += 1;
207        }
208        if !matches!(self.content_language, ContentLanguage::None) {
209            l += 1;
210        }
211        if self.schema.is_some() {
212            l += 1;
213        }
214        l
215    }
216
217    /// method to hash(keccak256) the cbor encoded bytes of this instance
218    pub fn hash(&self, as_rain_meta_document: bool) -> Result<[u8; 32], Error> {
219        if as_rain_meta_document {
220            Ok(keccak256(Self::cbor_encode_seq(
221                &vec![self.clone()],
222                KnownMagic::RainMetaDocumentV1,
223            )?)
224            .0)
225        } else {
226            Ok(keccak256(self.cbor_encode()?).0)
227        }
228    }
229
230    /// method to cbor encode
231    pub fn cbor_encode(&self) -> Result<Vec<u8>, Error> {
232        let mut bytes: Vec<u8> = vec![];
233        Ok(serde_cbor::to_writer(&mut bytes, &self).map(|_| bytes)?)
234    }
235
236    /// builds a cbor sequence from given MetaMaps
237    pub fn cbor_encode_seq(
238        seq: &Vec<RainMetaDocumentV1Item>,
239        magic: KnownMagic,
240    ) -> Result<Vec<u8>, Error> {
241        let mut bytes: Vec<u8> = magic.to_prefix_bytes().to_vec();
242        for item in seq {
243            serde_cbor::to_writer(&mut bytes, &item)?;
244        }
245        Ok(bytes)
246    }
247
248    /// method to cbor decode from given bytes
249    pub fn cbor_decode(data: &[u8]) -> Result<Vec<RainMetaDocumentV1Item>, Error> {
250        // The document prefix is not optional. metadata-v1.md: "Tooling that
251        // wishes to read meta MUST discard/ignore all binary data that does
252        // not begin with the magic number." This is the document decoder, so
253        // this is where that rule lives - not re-applied by whichever caller
254        // remembers to, which is how `store_content` came to decode any bytes
255        // and then check the prefix by hand afterwards. A single bare item
256        // has its own decoder, `RainMetaDocumentV1Item`'s `Deserialize`.
257        // rainlanguage/rain.metadata#203.
258        let Some(body) = data.strip_prefix(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()[..])
259        else {
260            return Err(Error::NotRainMetaDocument);
261        };
262        let len = body.len();
263        let mut metas: Vec<RainMetaDocumentV1Item> = vec![];
264        let mut consumed: usize = 0;
265        let mut deserializer = serde_cbor::Deserializer::from_slice(body);
266        // straight off the stream, not via serde_cbor::Value, whose BTreeMap
267        // silently collapses the duplicate keys the visitor has to reject.
268        //
269        // ItemOrDropped rather than Self: this is the sequence decoder, so an
270        // item this version does not read is skipped over instead of taking
271        // the items beside it down. Its bytes are still consumed and still
272        // counted, so the trailing len check below stays a statement about
273        // the whole document.
274        while match ItemOrDropped::deserialize(&mut deserializer) {
275            Ok(decoded) => {
276                consumed = deserializer.byte_offset();
277                if let ItemOrDropped::Item(meta) = decoded {
278                    metas.push(meta);
279                }
280                true
281            }
282            Err(error) => {
283                if error.is_eof() {
284                    false
285                } else {
286                    Err(Error::SerdeCborError(error))?
287                }
288            }
289        } {}
290
291        if metas.is_empty() || len != consumed {
292            Err(Error::CorruptMeta)?
293        }
294        Ok(metas)
295    }
296
297    // unpack the payload based on the configuration
298    pub fn unpack(&self) -> Result<Vec<u8>, Error> {
299        ContentEncoding::decode(&self.content_encoding, self.payload.as_ref())
300    }
301
302    // unpacks the payload to given meta type based on configuration
303    pub fn unpack_into<T: TryFrom<Self, Error = Error>>(self) -> Result<T, Error> {
304        match self.magic {
305            KnownMagic::OpMetaV1
306            | KnownMagic::DotrainV1
307            | KnownMagic::RainlangV1
308            | KnownMagic::SolidityAbiV2
309            | KnownMagic::AuthoringMetaV1
310            | KnownMagic::AuthoringMetaV2
311            | KnownMagic::AddressList
312            | KnownMagic::InterpreterCallerMetaV1
313            | KnownMagic::ExpressionDeployerV2BytecodeV1
314            | KnownMagic::DotrainSourceV1
315            | KnownMagic::OrderBuilderStateV1
316            | KnownMagic::RainlangSourceV1
317            | KnownMagic::RaindexSignedContextOracleV1 => T::try_from(self),
318            _ => Err(Error::UnsupportedMeta)?,
319        }
320    }
321}
322
323impl Serialize for RainMetaDocumentV1Item {
324    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
325        let mut map = serializer.serialize_map(Some(self.len()))?;
326        map.serialize_entry(&0, &self.payload)?;
327        map.serialize_entry(&1, &(self.magic as u64))?;
328        match self.content_type {
329            ContentType::None => {}
330            content_type => map.serialize_entry(&2, &content_type)?,
331        }
332        match self.content_encoding {
333            ContentEncoding::None => {}
334            content_encoding => map.serialize_entry(&3, &content_encoding)?,
335        }
336        match self.content_language {
337            ContentLanguage::None => {}
338            content_language => map.serialize_entry(&4, &content_language)?,
339        }
340        if let Some(schema) = &self.schema {
341            map.serialize_entry(&(KnownMagic::OaSchema as u64), schema)?;
342        }
343        map.end()
344    }
345}
346
347/// What a cbor item in a rain meta sequence turned out to be.
348///
349/// Dropped covers exactly one thing: a well formed item whose magic number
350/// this version does not know. The magic is the format's extension point -
351/// "Tooling can efficiently O(1) drop/ignore meta that it does not need or
352/// support decoding and parsing for", "feel free to build systems and
353/// applications with your own numbers and interpretations" - so somebody
354/// else's item travelling in the same document is skipped, not fatal to the
355/// items beside it. rainlanguage/rain.metadata#186.
356///
357/// Everything else malformed stops the document. One document is one emission
358/// from one emitter, whose prefix claims rain meta for the lot, and an
359/// emission carrying a broken claim is not mined for its readable parts.
360/// That is why an item omitting mandatory key 0 or 1 errors here even though
361/// metadata-v1.md line 237 reads as item level drop/ignore: it cannot even be
362/// identified, and recovering its siblings would accept selected data from an
363/// emitter who sent junk. rainlanguage/rain.metadata#188 is overruled on that
364/// line rather than implemented.
365enum ItemOrDropped {
366    Item(RainMetaDocumentV1Item),
367    /// Well formed cbor, but not an item this version reads.
368    Dropped,
369}
370
371impl<'de> Deserialize<'de> for ItemOrDropped {
372    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
373        /// RFC 8949 ยง5.6: a map with duplicate keys is not valid, so a second
374        /// sighting of a key is an error rather than an overwrite.
375        fn set_once<V, E: serde::de::Error>(
376            slot: &mut Option<V>,
377            value: V,
378            field: &'static str,
379        ) -> Result<(), E> {
380            if slot.is_some() {
381                return Err(serde::de::Error::duplicate_field(field));
382            }
383            *slot = Some(value);
384            Ok(())
385        }
386
387        struct EncodedMap;
388        impl<'de> Visitor<'de> for EncodedMap {
389            type Value = ItemOrDropped;
390
391            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
392                formatter.write_str("rain meta cbor encoded bytes")
393            }
394
395            fn visit_map<T: serde::de::MapAccess<'de>>(
396                self,
397                mut map: T,
398            ) -> Result<Self::Value, T::Error> {
399                const OA_SCHEMA_KEY: u64 = KnownMagic::OaSchema as u64;
400                let mut payload = None;
401                let mut magic: Option<u64> = None;
402                let mut content_type = None;
403                let mut content_encoding = None;
404                let mut content_language = None;
405                let mut schema = None;
406                // the recognised keys guard themselves through set_once; an
407                // unknown key has no slot to be occupied, so its repeats are
408                // tracked here
409                let mut unknown_keys: BTreeSet<u64> = BTreeSet::new();
410                while match map.next_key::<u64>() {
411                    Ok(Some(key)) => {
412                        match key {
413                            0 => set_once(&mut payload, map.next_value()?, "payload")?,
414                            1 => set_once(&mut magic, map.next_value()?, "magic number")?,
415                            2 => set_once(&mut content_type, map.next_value()?, "content type")?,
416                            3 => set_once(
417                                &mut content_encoding,
418                                map.next_value()?,
419                                "content encoding",
420                            )?,
421                            4 => set_once(
422                                &mut content_language,
423                                map.next_value()?,
424                                "content language",
425                            )?,
426                            OA_SCHEMA_KEY => set_once(&mut schema, map.next_value()?, "schema")?,
427                            // the map structure exists so later conventions can
428                            // add indexes that older tooling adopts "or not" in
429                            // a backwards compatible way, so an index this
430                            // version does not know is skipped, not an error.
431                            // ยง5.6 does not ask whether the decoder understands
432                            // a key, so the repeat of one is still invalid even
433                            // though the value behind it is never read.
434                            _ => {
435                                if !unknown_keys.insert(key) {
436                                    return Err(serde::de::Error::custom(format!(
437                                        "duplicate map key: {key}"
438                                    )));
439                                }
440                                map.next_value::<serde::de::IgnoredAny>()?;
441                            }
442                        };
443                        true
444                    }
445                    Ok(None) => false,
446                    Err(error) => Err(error)?,
447                } {}
448                // Indexes 0 and 1 are mandatory. An item without them is not
449                // dropped like an unknown magic below: it cannot even be
450                // identified, and it sits inside a document whose prefix is
451                // the emitter claiming rain meta. One document is one emission
452                // from one emitter, and an emission carrying a malformed item
453                // is not mined for its readable parts - the same rule
454                // fetch_by_subject applies to a failed claim at the item
455                // payload level. A foreign magic is somebody else's data
456                // travelling alongside ours; a keyless map is a broken claim.
457                let (Some(payload), Some(magic_number)) = (payload, magic) else {
458                    return Err(serde::de::Error::custom(
459                        "cbor item omits a mandatory key (0 payload, 1 magic)",
460                    ));
461                };
462
463                // A nested document prefix is not somebody else's magic
464                // number, it is this crate's own in a position it cannot
465                // occupy: the document prefix is the first 8 bytes of the
466                // whole document, never an item's cbor key 1. That is
467                // malformed structure rather than a type this version does
468                // not read, so it stops the document instead of being
469                // dropped with the unknown numbers below.
470                // rainlanguage/rain.metadata#204.
471                if magic_number == KnownMagic::RainMetaDocumentV1 as u64 {
472                    return Err(serde::de::Error::custom(
473                        "rain meta document magic number as an item magic number",
474                    ));
475                }
476
477                let Ok(magic) = KnownMagic::try_from(magic_number) else {
478                    return Ok(ItemOrDropped::Dropped);
479                };
480
481                let content_type = content_type.unwrap_or(ContentType::None);
482                let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
483                let content_language = content_language.unwrap_or(ContentLanguage::None);
484
485                Ok(ItemOrDropped::Item(RainMetaDocumentV1Item {
486                    payload,
487                    magic,
488                    content_type,
489                    content_encoding,
490                    content_language,
491                    schema,
492                }))
493            }
494        }
495        deserializer.deserialize_map(EncodedMap)
496    }
497}
498
499/// Decoding one item on its own is strict.
500///
501/// The drop rule is about a *sequence*: an item nobody claimed this tool would
502/// read must not take the items beside it down with it. Asking for a single
503/// item names the thing you want, so not getting it is an error rather than a
504/// silent absence, and there is nothing beside it to protect.
505impl<'de> Deserialize<'de> for RainMetaDocumentV1Item {
506    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
507        match ItemOrDropped::deserialize(deserializer)? {
508            ItemOrDropped::Item(item) => Ok(item),
509            ItemOrDropped::Dropped => Err(serde::de::Error::custom(
510                "not a rain meta item this version reads: a mandatory key is missing or the magic number is unknown",
511            )),
512        }
513    }
514}
515
516/// searches for a meta matching the given hash in given subgraphs urls
517pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
518    // future::select_ok panics on an empty iterator.
519    if subgraphs.is_empty() {
520        return Err(Error::NoRecordFound);
521    }
522    let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
523        hash: Some(hash.to_ascii_lowercase()),
524    });
525    let mut promises = vec![];
526
527    let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
528    for url in subgraphs {
529        promises.push(Box::pin(query::process_meta_query(
530            client.clone(),
531            &request_body,
532            url,
533        )));
534    }
535    let response_value = future::select_ok(promises.drain(..)).await?.0;
536    Ok(response_value)
537}
538
539/// checks if the given contract implements IDescribeByMetaV1 interface
540///
541/// `Err` is rain-erc's "answer unknown": a transport or decode failure stopped
542/// the probe, never a contract that lacks the interface. Both probes here can
543/// raise it - the erc165 one this delegates to rain-erc, and the interface id
544/// one below - and neither may be flattened into `Ok(false)`, which is the
545/// contract answering "no".
546pub async fn implements_i_described_by_meta_v1<P: Provider>(
547    provider: &P,
548    contract_address: Address,
549) -> Result<bool, Erc165Error> {
550    if !supports_erc165(provider, contract_address).await? {
551        return Ok(false);
552    }
553
554    let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
555    if interface_id_res.is_err() {
556        return Ok(false);
557    }
558
559    match IERC165::new(contract_address, provider)
560        .supportsInterface(interface_id_res.unwrap().into())
561        .call()
562        .await
563    {
564        Ok(supported) => Ok(supported),
565        Err(error)
566            if error.as_revert_data().is_some()
567                || matches!(error, ContractError::ZeroData(_, _)) =>
568        {
569            Ok(false)
570        }
571        Err(error) => Err(error.into()),
572    }
573}
574
575/// # Meta Storage(CAS)
576///
577/// In-memory CAS (content addressed storage) for Rain metadata which basically stores
578/// k/v pairs of meta hash and meta bytes, as well as providing functionalities to
579/// easliy read/write to the CAS.
580///
581/// Hashes are normal bytes and meta bytes are valid cbor encoded as data bytes.
582///
583/// ## Examples
584///
585/// ```
586/// use rain_metadata::Store;
587/// use rain_metadata::meta::cache::MetaCache;
588/// use std::collections::HashMap;
589///
590/// // to instantiate with an empty subgraph list
591/// let mut store = Store::new();
592///
593/// // or to instantiate with initial values
594/// let mut store = Store::create(
595///     &vec!["sg-url-1".to_string()],
596///     &MetaCache::default(),
597///     &HashMap::new(),
598/// );
599///
600/// // add a new subgraph endpoint url to the subgraph list
601/// store.add_subgraphs(&vec!["sg-url-2".to_string()]);
602///
603/// // merge another Store into this one
604/// store.merge(&Store::new());
605///
606/// // updates the meta store with some bytes and the hash they hash to - a
607/// // pair that does not is refused, so the hash is derived rather than picked
608/// let bytes = vec![0u8, 1u8];
609/// let hash = alloy::primitives::keccak256(&bytes).0.to_vec();
610/// store.update_with(&hash, &bytes).unwrap();
611///
612/// // `Store::update(&hash)` is async; it searches each subgraph for `hash` and
613/// // populates the cache with the result. Call it from an async context with `.await`.
614///
615/// // to get a record from the store
616/// let _meta = store.get_meta(&hash);
617///
618/// // Store is agnostic to dotrain contents โ€” it just maps the hash of the content
619/// // to the given uri and puts it as a new meta into the meta cache.
620/// let dotrain_uri = "path/to/file.rain";
621/// let dotrain_content = "/* some dotrain source */";
622/// let (_new_hash, _old_hash) = store
623///     .set_dotrain(dotrain_content, dotrain_uri, false)
624///     .unwrap();
625///
626/// // to get dotrain meta bytes given a uri
627/// let _dotrain_meta_bytes = store.get_dotrain_meta(dotrain_uri);
628/// ```
629#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
630pub struct Store {
631    subgraphs: Vec<String>,
632    cache: MetaCache,
633    dotrain_cache: HashMap<String, Vec<u8>>,
634}
635
636impl Default for Store {
637    fn default() -> Self {
638        Store::new()
639    }
640}
641
642impl Store {
643    /// lazily creates a new instance with no subgraphs
644    /// it is recommended to use create() instead with initial values
645    pub fn new() -> Store {
646        Store {
647            subgraphs: vec![],
648            cache: MetaCache::default(),
649            dotrain_cache: HashMap::new(),
650        }
651    }
652
653    /// creates new instance of Store with given initial values
654    /// it checks the validity of each item of the provided values and only stores those that are valid
655    pub fn create(
656        subgraphs: &Vec<String>,
657        cache: &MetaCache,
658        dotrain_cache: &HashMap<String, Vec<u8>>,
659    ) -> Store {
660        let mut store = Store::new();
661        store.add_subgraphs(subgraphs);
662        for (hash, bytes) in cache.iter() {
663            let _ = store.update_with(hash, bytes);
664        }
665        for (uri, hash) in dotrain_cache {
666            if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
667                store.dotrain_cache.insert(uri.clone(), hash.clone());
668            }
669        }
670        store
671    }
672
673    /// all subgraph endpoints in this instance
674    pub fn subgraphs(&self) -> &Vec<String> {
675        &self.subgraphs
676    }
677
678    /// add new subgraph endpoints
679    pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
680        for sg in subgraphs {
681            if !self.subgraphs.contains(sg) {
682                self.subgraphs.push(sg.to_string());
683            }
684        }
685    }
686
687    /// getter method for the whole meta cache
688    pub fn cache(&self) -> &MetaCache {
689        &self.cache
690    }
691
692    /// get the corresponding meta bytes of the given hash if it exists
693    pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
694        self.cache.get(hash)
695    }
696
697    /// getter method for the whole dotrain cache
698    pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
699        &self.dotrain_cache
700    }
701
702    /// get the corresponding dotrain hash of the given dotrain uri if it exists
703    pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
704        self.dotrain_cache.get(uri)
705    }
706
707    /// get the corresponding uri of the given dotrain hash if it exists
708    pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
709        for (uri, h) in &self.dotrain_cache {
710            if h == hash {
711                return Some(uri);
712            }
713        }
714        None
715    }
716
717    /// get the corresponding meta bytes of the given dotrain uri if it exists
718    pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
719        self.get_meta(self.dotrain_cache.get(uri)?)
720    }
721
722    /// deletes a dotrain record given a uri, `keep_meta = false` drops the meta
723    /// bytes only when no other dotrain uri still maps to them
724    pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
725        if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
726            if !keep_meta {
727                self.remove_unreferenced_meta(&kv.1);
728            }
729        };
730    }
731
732    /// call only once `dotrain_cache` no longer maps the dropped uri to `hash`
733    fn remove_unreferenced_meta(&mut self, hash: &[u8]) {
734        if !self.dotrain_cache.values().any(|h| h == hash) {
735            self.cache.remove(hash);
736        }
737    }
738
739    /// lazilly merges another Store to the current one, avoids duplicates
740    /// every map keeps the entry this Store already has on a key collision
741    pub fn merge(&mut self, other: &Store) {
742        self.add_subgraphs(&other.subgraphs);
743        for (hash, bytes) in other.cache.iter() {
744            if !self.cache.contains_key(hash) {
745                // entries are verified by construction, so copying one cannot
746                // introduce an unverified entry
747                let _ = self.cache.insert_verified(hash, bytes.clone());
748            }
749        }
750        for (uri, hash) in &other.dotrain_cache {
751            if !self.dotrain_cache.contains_key(uri) {
752                self.dotrain_cache.insert(uri.clone(), hash.clone());
753            }
754        }
755    }
756
757    /// Caches `bytes` under `hash` via [MetaCache::insert_verified], then
758    /// unpacks the items they carry into the cache too. The gate itself lives
759    /// on [MetaCache], which has no other way in.
760    fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
761        self.cache.insert_verified(hash, bytes.clone())?;
762        self.store_content(&bytes);
763        self.get_meta(hash).ok_or(Error::NoRecordFound)
764    }
765
766    /// updates the meta cache by searching through all subgraphs for the given
767    /// hash, and returns the reference to the meta bytes in the cache if it was
768    /// found. Refreshes unconditionally; [Self::update_check] is the variant
769    /// that leaves an already cached hash alone.
770    pub async fn update(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
771        let meta = search(&hex::encode_prefixed(hash), &self.subgraphs).await?;
772        self.insert_verified(hash, meta.bytes)
773    }
774
775    /// first checks if the meta is stored, if not will perform update()
776    pub async fn update_check(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
777        // The NoRecordFound arm is unreachable, contains_key having just
778        // proved the key is present. It is spelled this way rather than as
779        // `if let Some(cached) = self.get_meta(hash)` because that holds an
780        // immutable borrow of self across the mutable call below it, which
781        // the borrow checker refuses.
782        if self.cache.contains_key(hash) {
783            return self.get_meta(hash).ok_or(Error::NoRecordFound);
784        }
785        self.update(hash).await
786    }
787
788    /// updates the meta cache with the given hash and meta bytes, and returns
789    /// the reference to the bytes if they were accepted. Leaves an already
790    /// cached hash alone, as [Self::update_check] does for the subgraph path.
791    pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Result<&Vec<u8>, Error> {
792        // The NoRecordFound arm is unreachable, contains_key having just
793        // proved the key is present. It is spelled this way rather than as
794        // `if let Some(cached) = self.get_meta(hash)` because that holds an
795        // immutable borrow of self across the mutable call below it, which
796        // the borrow checker refuses.
797        if self.cache.contains_key(hash) {
798            return self.get_meta(hash).ok_or(Error::NoRecordFound);
799        }
800        self.insert_verified(hash, bytes.to_vec())
801    }
802
803    /// stores (or updates in case the URI already exists) the given dotrain text as meta into the store cache
804    /// and maps it to the given uri (path), it should be noted that reading the content of the dotrain is not in
805    /// the scope of Store and handling and passing on a correct URI (path) for the given text must be handled
806    /// externally by the implementer, `keep_old = false` drops the replaced meta
807    /// bytes only when no other dotrain uri still maps to them
808    pub fn set_dotrain(
809        &mut self,
810        text: &str,
811        uri: &str,
812        keep_old: bool,
813    ) -> Result<(Vec<u8>, Vec<u8>), Error> {
814        let bytes = RainMetaDocumentV1Item {
815            payload: serde_bytes::ByteBuf::from(text.as_bytes()),
816            magic: KnownMagic::DotrainV1,
817            content_type: ContentType::OctetStream,
818            content_encoding: ContentEncoding::None,
819            content_language: ContentLanguage::None,
820            schema: None,
821        }
822        .cbor_encode()?;
823        let new_hash = keccak256(&bytes).0.to_vec();
824        if let Some(h) = self.dotrain_cache.get(uri) {
825            let old_hash = h.clone();
826            if new_hash == old_hash {
827                self.cache.insert_verified(&new_hash, bytes)?;
828                Ok((new_hash, vec![]))
829            } else {
830                self.cache.insert_verified(&new_hash, bytes)?;
831                self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
832                if !keep_old {
833                    self.remove_unreferenced_meta(&old_hash);
834                }
835                Ok((new_hash, old_hash))
836            }
837        } else {
838            self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
839            self.cache.insert_verified(&new_hash, bytes)?;
840            Ok((new_hash, vec![]))
841        }
842    }
843
844    /// decodes each meta and stores the inner meta items into the cache
845    /// if any of the inner items is an authoring meta, stores it in authoring meta cache as well
846    /// returns the reference to the authoring bytes if the meta bytes contained any
847    fn store_content(&mut self, bytes: &[u8]) {
848        // cbor_decode only succeeds on a rain meta document, so bytes that
849        // are not one - a bare item cached under its own digest, say - fall
850        // through here untouched. This used to decode anything and then check
851        // the prefix by hand before unpacking; the decoder owns that rule now.
852        if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
853            for meta_map in &meta_maps {
854                if let Ok(encoded_bytes) = meta_map.cbor_encode() {
855                    // the key is this item's own digest, so the gate can
856                    // only pass - routing through it anyway means no
857                    // reader has to work that out
858                    let _ = self
859                        .cache
860                        .insert_verified(&keccak256(&encoded_bytes).0, encoded_bytes);
861                }
862            }
863        }
864    }
865}
866
867/// converts string to bytes32
868///
869/// Right padding with `0u8` is the encoding, so [`bytes32_to_str`] ends the
870/// string at the first `0u8` and cannot carry one. An input holding a nul is
871/// rejected rather than round tripped into a shorter string.
872pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
873    let bytes: &[u8] = text.as_bytes();
874    if bytes.len() > 32 {
875        return Err(Error::BiggerThan32Bytes);
876    }
877    if bytes.contains(&0u8) {
878        return Err(Error::NulByteInInput);
879    }
880    let mut b32 = [0u8; 32];
881    b32[..bytes.len()].copy_from_slice(bytes);
882    Ok(b32)
883}
884
885/// converts bytes32 to string
886pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
887    let mut len = 32;
888    if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
889        len = pos;
890    };
891    Ok(std::str::from_utf8(&bytes[..len])?)
892}
893
894#[cfg(all(test, not(target_family = "wasm")))]
895mod tests {
896    use super::{
897        *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
898        ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
899    };
900    use alloy::providers::ProviderBuilder;
901    use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
902    use serde_json::json;
903
904    /// Roundtrip test for an authoring meta
905    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
906    #[test]
907    fn authoring_meta_roundtrip() -> Result<(), Error> {
908        let authoring_meta_content = r#"[
909            {
910                "word": "stack",
911                "description": "Copies an existing value from the stack.",
912                "operandParserOffset": 16
913            },
914            {
915                "word": "constant",
916                "description": "Copies a constant value onto the stack.",
917                "operandParserOffset": 16
918            }
919        ]"#;
920        let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
921
922        // abi encode the authoring meta with performing validation
923        let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
924        let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
925            (
926                str_to_bytes32("stack")?,
927                16u8,
928                "Copies an existing value from the stack.".to_string(),
929            ),
930            (
931                str_to_bytes32("constant")?,
932                16u8,
933                "Copies a constant value onto the stack.".to_string(),
934            ),
935        ]);
936        // check the encoded bytes agaiinst the expected
937        assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
938
939        let meta_map = RainMetaDocumentV1Item {
940            payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
941            magic: KnownMagic::AuthoringMetaV1,
942            content_type: ContentType::Cbor,
943            content_encoding: ContentEncoding::None,
944            content_language: ContentLanguage::None,
945            schema: None,
946        };
947        let cbor_encoded = meta_map.cbor_encode()?;
948
949        // cbor map with 3 keys
950        assert_eq!(cbor_encoded[0], 0xa3);
951        // key 0
952        assert_eq!(cbor_encoded[1], 0x00);
953        // major type 2 (bytes) length 512
954        assert_eq!(cbor_encoded[2], 0b010_11001);
955        assert_eq!(cbor_encoded[3], 0b000_00010);
956        assert_eq!(cbor_encoded[4], 0b000_00000);
957        // payload
958        assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
959        // key 1
960        assert_eq!(cbor_encoded[517], 0x01);
961        // major type 0 (unsigned integer) value 27
962        assert_eq!(cbor_encoded[518], 0b000_11011);
963        // magic number
964        assert_eq!(
965            &cbor_encoded[519..527],
966            KnownMagic::AuthoringMetaV1.to_prefix_bytes()
967        );
968        // key 2
969        assert_eq!(cbor_encoded[527], 0x02);
970        // text string application/cbor length 16
971        assert_eq!(cbor_encoded[528], 0b011_10000);
972        // the string application/cbor, must be the end of data
973        assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
974
975        // decode the data back to MetaMap
976        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
977        // the length of decoded maps must be 1 as we only had 1 encoded item
978        assert_eq!(cbor_decoded.len(), 1);
979        // decoded item must be equal to the original meta_map
980        assert_eq!(cbor_decoded[0], meta_map);
981
982        Ok(())
983    }
984
985    /// Roundtrip test for a dotrain meta
986    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
987    #[test]
988    fn dotrain_meta_roundtrip() -> Result<(), Error> {
989        let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
990        let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
991
992        let content_encoding = ContentEncoding::Deflate;
993        let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
994
995        let meta_map = RainMetaDocumentV1Item {
996            payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
997            magic: KnownMagic::DotrainV1,
998            content_type: ContentType::OctetStream,
999            content_encoding,
1000            content_language: ContentLanguage::En,
1001            schema: None,
1002        };
1003        let cbor_encoded = meta_map.cbor_encode()?;
1004
1005        // cbor map with 5 keys
1006        assert_eq!(cbor_encoded[0], 0xa5);
1007        // key 0
1008        assert_eq!(cbor_encoded[1], 0x00);
1009        // major type 2 (bytes) length 36
1010        assert_eq!(cbor_encoded[2], 0b010_11000);
1011        assert_eq!(cbor_encoded[3], 0b001_00100);
1012        // assert_eq!(cbor_encoded[4], 0b000_00000);
1013        // payload
1014        assert_eq!(cbor_encoded[4..40], deflated_payload);
1015        // key 1
1016        assert_eq!(cbor_encoded[40], 0x01);
1017        // major type 0 (unsigned integer) value 27
1018        assert_eq!(cbor_encoded[41], 0b000_11011);
1019        // magic number
1020        assert_eq!(
1021            &cbor_encoded[42..50],
1022            KnownMagic::DotrainV1.to_prefix_bytes()
1023        );
1024        // key 2
1025        assert_eq!(cbor_encoded[50], 0x02);
1026        // text string application/octet-stream length 24
1027        assert_eq!(cbor_encoded[51], 0b011_11000);
1028        assert_eq!(cbor_encoded[52], 0b000_11000);
1029        // the string application/octet-stream
1030        assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1031        // key 3
1032        assert_eq!(cbor_encoded[77], 0x03);
1033        // text string deflate length 7
1034        assert_eq!(cbor_encoded[78], 0b011_00111);
1035        // the string deflate
1036        assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1037        // key 4
1038        assert_eq!(cbor_encoded[86], 0x04);
1039        // text string en length 2
1040        assert_eq!(cbor_encoded[87], 0b011_00010);
1041        // the string identity, must be the end of data
1042        assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1043
1044        // decode the data back to MetaMap
1045        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
1046        // the length of decoded maps must be 1 as we only had 1 encoded item
1047        assert_eq!(cbor_decoded.len(), 1);
1048        // decoded item must be equal to the original meta_map
1049        assert_eq!(cbor_decoded[0], meta_map);
1050
1051        Ok(())
1052    }
1053
1054    /// Roundtrip test for a meta sequence
1055    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
1056    #[test]
1057    fn meta_seq_roundtrip() -> Result<(), Error> {
1058        let authoring_meta_content = r#"[
1059            {
1060                "word": "stack",
1061                "description": "Copies an existing value from the stack.",
1062                "operandParserOffset": 16
1063            },
1064            {
1065                "word": "constant",
1066                "description": "Copies a constant value onto the stack.",
1067                "operandParserOffset": 16
1068            }
1069        ]"#;
1070        let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1071        let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1072        let meta_map_1 = RainMetaDocumentV1Item {
1073            payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1074            magic: KnownMagic::AuthoringMetaV1,
1075            content_type: ContentType::Cbor,
1076            content_encoding: ContentEncoding::None,
1077            content_language: ContentLanguage::None,
1078            schema: None,
1079        };
1080
1081        let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1082        let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1083        let content_encoding = ContentEncoding::Deflate;
1084        let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1085        let meta_map_2 = RainMetaDocumentV1Item {
1086            payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1087            magic: KnownMagic::DotrainV1,
1088            content_type: ContentType::OctetStream,
1089            content_encoding,
1090            content_language: ContentLanguage::En,
1091            schema: None,
1092        };
1093
1094        // cbor encode as RainMetaDocument sequence
1095        let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1096            &vec![meta_map_1.clone(), meta_map_2.clone()],
1097            KnownMagic::RainMetaDocumentV1,
1098        )?;
1099
1100        // 8 byte magic number prefix
1101        assert_eq!(
1102            &cbor_encoded[0..8],
1103            KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1104        );
1105
1106        // first item in the encoded bytes
1107        // cbor map with 3 keys
1108        assert_eq!(cbor_encoded[8], 0xa3);
1109        // key 0
1110        assert_eq!(cbor_encoded[9], 0x00);
1111        // major type 2 (bytes) length 512
1112        assert_eq!(cbor_encoded[10], 0b010_11001);
1113        assert_eq!(cbor_encoded[11], 0b000_00010);
1114        assert_eq!(cbor_encoded[12], 0b000_00000);
1115        // payload
1116        assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1117        // key 1
1118        assert_eq!(cbor_encoded[525], 0x01);
1119        // major type 0 (unsigned integer) value 27
1120        assert_eq!(cbor_encoded[526], 0b000_11011);
1121        // magic number
1122        assert_eq!(
1123            &cbor_encoded[527..535],
1124            KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1125        );
1126        // key 2
1127        assert_eq!(cbor_encoded[535], 0x02);
1128        // text string application/cbor length 16
1129        assert_eq!(cbor_encoded[536], 0b011_10000);
1130        // the string application/cbor, must be the end of data
1131        assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1132
1133        // second item in the encoded bytes
1134        // cbor map with 5 keys
1135        assert_eq!(cbor_encoded[553], 0xa5);
1136        // key 0
1137        assert_eq!(cbor_encoded[554], 0x00);
1138        // major type 2 (bytes) length 36
1139        assert_eq!(cbor_encoded[555], 0b010_11000);
1140        assert_eq!(cbor_encoded[556], 0b001_00100);
1141        // assert_eq!(cbor_encoded[4], 0b000_00000);
1142        // payload
1143        assert_eq!(cbor_encoded[557..593], deflated_payload);
1144        // key 1
1145        assert_eq!(cbor_encoded[593], 0x01);
1146        // major type 0 (unsigned integer) value 27
1147        assert_eq!(cbor_encoded[594], 0b000_11011);
1148        // magic number
1149        assert_eq!(
1150            &cbor_encoded[595..603],
1151            KnownMagic::DotrainV1.to_prefix_bytes()
1152        );
1153        // key 2
1154        assert_eq!(cbor_encoded[603], 0x02);
1155        // text string application/octet-stream length 24
1156        assert_eq!(cbor_encoded[604], 0b011_11000);
1157        assert_eq!(cbor_encoded[605], 0b000_11000);
1158        // the string application/octet-stream
1159        assert_eq!(
1160            &cbor_encoded[606..630],
1161            "application/octet-stream".as_bytes()
1162        );
1163        // key 3
1164        assert_eq!(cbor_encoded[630], 0x03);
1165        // text string deflate length 7
1166        assert_eq!(cbor_encoded[631], 0b011_00111);
1167        // the string deflate
1168        assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1169        // key 4
1170        assert_eq!(cbor_encoded[639], 0x04);
1171        // text string en length 2
1172        assert_eq!(cbor_encoded[640], 0b011_00010);
1173        // the string identity, must be the end of data
1174        assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1175
1176        // decode the data back to MetaMap
1177        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1178        // the length of decoded maps must be 2 as we had 2 encoded item
1179        assert_eq!(cbor_decoded.len(), 2);
1180
1181        // decoded item 1 must be equal to the original meta_map_1
1182        assert_eq!(cbor_decoded[0], meta_map_1);
1183        // decoded item 2 must be equal to the original meta_map_2
1184        assert_eq!(cbor_decoded[1], meta_map_2);
1185
1186        Ok(())
1187    }
1188
1189    #[test]
1190    fn test_bytes32_to_str() {
1191        let text_bytes_list = vec![
1192            (
1193                "",
1194                hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1195            ),
1196            (
1197                "A",
1198                hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1199            ),
1200            (
1201                "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1202                hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1203            ),
1204            (
1205                "!@#$%^&*(),./;'[]",
1206                hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1207            ),
1208        ];
1209
1210        for (text, bytes) in text_bytes_list {
1211            assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1212        }
1213    }
1214
1215    #[test]
1216    fn test_str_to_bytes32() {
1217        let text_bytes_list = vec![
1218            (
1219                "",
1220                hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1221            ),
1222            (
1223                "A",
1224                hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1225            ),
1226            (
1227                "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1228                hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1229            ),
1230            (
1231                "!@#$%^&*(),./;'[]",
1232                hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1233            ),
1234        ];
1235
1236        for (text, bytes) in text_bytes_list {
1237            assert_eq!(bytes, str_to_bytes32(text).unwrap());
1238        }
1239    }
1240
1241    #[test]
1242    fn test_str_to_bytes32_long() {
1243        assert!(matches!(
1244            str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1245            Error::BiggerThan32Bytes
1246        ));
1247    }
1248
1249    /// A nul cannot survive the padding convention bytes32_to_str decodes, so
1250    /// it is rejected on the way in wherever it sits, including the pair the
1251    /// issue collides ("a" and "a\0").
1252    #[test]
1253    fn test_str_to_bytes32_rejects_nul() {
1254        for text in [
1255            "\0",
1256            "\0a",
1257            "a\0",
1258            "a\0b",
1259            "abcdefghijklmnopqrstuvwxyz01234\0",
1260        ] {
1261            assert!(
1262                matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1263                "nul bearing input {:?} accepted",
1264                text
1265            );
1266        }
1267    }
1268
1269    /// Everything str_to_bytes32 accepts comes back out of bytes32_to_str
1270    /// unchanged, and no two of them share a bytes32.
1271    #[test]
1272    fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1273        let mut seen: Vec<[u8; 32]> = vec![];
1274        for text in [
1275            "",
1276            "a",
1277            "stack",
1278            "!@#$%^&*(),./;'[]",
1279            "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1280        ] {
1281            let bytes = str_to_bytes32(text)?;
1282            assert_eq!(bytes32_to_str(&bytes)?, text);
1283            assert!(!seen.contains(&bytes), "input {:?} collided", text);
1284            seen.push(bytes);
1285        }
1286        Ok(())
1287    }
1288
1289    #[tokio::test]
1290    async fn test_implements_i_describe_by_meta_v1() {
1291        // makes new server/client with success response for erc165 check
1292        async fn new_server_client() -> (Asserter, impl Provider) {
1293            let asserter = Asserter::new();
1294            let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1295
1296            // Mock a responses for successful supports erc165 check
1297            asserter.push_success(
1298                &"0x0000000000000000000000000000000000000000000000000000000000000001",
1299            );
1300            asserter.push_success(
1301                &"0x0000000000000000000000000000000000000000000000000000000000000000",
1302            );
1303
1304            (asserter, provider)
1305        }
1306
1307        let address = Address::random();
1308
1309        // mock a true response for implements IDescribedByMetaV1
1310        let (asserter, provider) = new_server_client().await;
1311        asserter
1312            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1313        let result = implements_i_described_by_meta_v1(&provider, address)
1314            .await
1315            .unwrap();
1316        assert!(result);
1317
1318        // mock a false response for implements IDescribedByMetaV1
1319        let (asserter, provider) = new_server_client().await;
1320        asserter
1321            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1322        let result = implements_i_described_by_meta_v1(&provider, address)
1323            .await
1324            .unwrap();
1325        assert!(!result);
1326
1327        // mock a revert response for implements IDescribedByMetaV1
1328        let (asserter, provider) = new_server_client().await;
1329        asserter.push_failure(ErrorPayload {
1330            code: -32003,
1331            message: "execution reverted".into(),
1332            data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1333        });
1334        let result = implements_i_described_by_meta_v1(&provider, address)
1335            .await
1336            .unwrap();
1337        assert!(!result);
1338    }
1339
1340    /// Roundtrip test for a meta map carrying the OaSchema magic number as an
1341    /// additional CBOR map key beyond the standard 0-4 keys.
1342    /// MetaMap (with schema) -> cbor encode -> cbor decode -> MetaMap, assert equality
1343    #[test]
1344    fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1345        let payload = vec![0x01, 0x02, 0x03];
1346        // an IPFS hash referencing the schema of the payload, as written by
1347        // the SFT frontend under the OaSchema map key
1348        let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1349        assert_eq!(schema.len(), 46);
1350
1351        let meta_map = RainMetaDocumentV1Item {
1352            payload: serde_bytes::ByteBuf::from(payload.clone()),
1353            magic: KnownMagic::OaStructure,
1354            content_type: ContentType::Json,
1355            content_encoding: ContentEncoding::Deflate,
1356            content_language: ContentLanguage::None,
1357            schema: Some(schema.clone()),
1358        };
1359        let cbor_encoded = meta_map.cbor_encode()?;
1360
1361        // cbor map with 5 keys (0, 1, 2, 3 and the OaSchema magic)
1362        assert_eq!(cbor_encoded[0], 0xa5);
1363        // key 0
1364        assert_eq!(cbor_encoded[1], 0x00);
1365        // major type 2 (bytes) length 3
1366        assert_eq!(cbor_encoded[2], 0b010_00011);
1367        // payload
1368        assert_eq!(cbor_encoded[3..6], payload);
1369        // key 1
1370        assert_eq!(cbor_encoded[6], 0x01);
1371        // major type 0 (unsigned integer) value 27
1372        assert_eq!(cbor_encoded[7], 0b000_11011);
1373        // magic number
1374        assert_eq!(
1375            &cbor_encoded[8..16],
1376            KnownMagic::OaStructure.to_prefix_bytes()
1377        );
1378        // key 2
1379        assert_eq!(cbor_encoded[16], 0x02);
1380        // text string application/json length 16
1381        assert_eq!(cbor_encoded[17], 0b011_10000);
1382        assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1383        // key 3
1384        assert_eq!(cbor_encoded[34], 0x03);
1385        // text string deflate length 7
1386        assert_eq!(cbor_encoded[35], 0b011_00111);
1387        assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1388        // the OaSchema magic as key, major type 0 (unsigned integer) value 27
1389        assert_eq!(cbor_encoded[43], 0b000_11011);
1390        assert_eq!(
1391            &cbor_encoded[44..52],
1392            KnownMagic::OaSchema.to_prefix_bytes()
1393        );
1394        // schema value, text string length 46
1395        assert_eq!(cbor_encoded[52], 0b011_11000);
1396        assert_eq!(cbor_encoded[53], 46);
1397        // the schema hash string, must be the end of data
1398        assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1399
1400        // decode the data back to MetaMap
1401        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
1402        // the length of decoded maps must be 1 as we only had 1 encoded item
1403        assert_eq!(cbor_decoded.len(), 1);
1404        // decoded item must be equal to the original meta_map
1405        assert_eq!(cbor_decoded[0], meta_map);
1406
1407        Ok(())
1408    }
1409
1410    /// A meta map without the schema key must keep encoding exactly as before
1411    /// (no schema entry on the wire) and roundtrip with schema None
1412    #[test]
1413    fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1414        let payload = vec![0x0a, 0x0b];
1415        let meta_map = RainMetaDocumentV1Item {
1416            payload: serde_bytes::ByteBuf::from(payload.clone()),
1417            magic: KnownMagic::OaStructure,
1418            content_type: ContentType::None,
1419            content_encoding: ContentEncoding::None,
1420            content_language: ContentLanguage::None,
1421            schema: None,
1422        };
1423        let cbor_encoded = meta_map.cbor_encode()?;
1424
1425        // cbor map with only the 2 mandatory keys
1426        assert_eq!(cbor_encoded[0], 0xa2);
1427        // key 0
1428        assert_eq!(cbor_encoded[1], 0x00);
1429        // major type 2 (bytes) length 2
1430        assert_eq!(cbor_encoded[2], 0b010_00010);
1431        // payload
1432        assert_eq!(cbor_encoded[3..5], payload);
1433        // key 1
1434        assert_eq!(cbor_encoded[5], 0x01);
1435        // major type 0 (unsigned integer) value 27
1436        assert_eq!(cbor_encoded[6], 0b000_11011);
1437        // magic number, must be the end of data
1438        assert_eq!(
1439            &cbor_encoded[7..],
1440            KnownMagic::OaStructure.to_prefix_bytes()
1441        );
1442
1443        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
1444        assert_eq!(cbor_decoded.len(), 1);
1445        assert_eq!(cbor_decoded[0], meta_map);
1446
1447        Ok(())
1448    }
1449
1450    /// A map key this version has no meaning for is a future index, so it is
1451    /// skipped and the rest of the map decodes
1452    #[test]
1453    fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1454        let mut bytes: Vec<u8> = vec![
1455            // cbor map with 3 keys
1456            0xa3, // key 0, bytes payload of length 0
1457            0x00, 0x40, // key 1, unsigned integer magic number
1458            0x01, 0x1b,
1459        ];
1460        bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1461        // key 5, a plausible future index, unsigned integer value 7
1462        bytes.extend_from_slice(&[0x05, 0x07]);
1463
1464        let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1465        assert_eq!(decoded.len(), 1);
1466        assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1467
1468        Ok(())
1469    }
1470
1471    /// A magic number other than OaSchema used as an extra map key is a future
1472    /// magic keyed entry, skipped the same way, and leaves schema unset
1473    #[test]
1474    fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1475        // build a map identical to a valid 2 key meta map but with an extra
1476        // OaHashList magic key carrying a text string
1477        let mut bytes: Vec<u8> = vec![
1478            // cbor map with 3 keys
1479            0xa3, // key 0, bytes payload of length 1
1480            0x00, 0x41, 0xff, // key 1, unsigned integer magic number
1481            0x01, 0x1b,
1482        ];
1483        bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1484        // the OaHashList magic as key
1485        bytes.push(0x1b);
1486        bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1487        // text string value of length 2
1488        bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1489
1490        let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1491        assert_eq!(decoded.len(), 1);
1492        let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1493        assert_eq!(decoded[0], expected);
1494        assert_eq!(decoded[0].schema, None);
1495
1496        Ok(())
1497    }
1498
1499    /// The whole value of an unknown key is consumed however nested, so the
1500    /// item that follows it in the sequence still decodes
1501    #[test]
1502    fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1503        let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1504        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1505        // key 5, value {42: [1, 2]}
1506        bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1507        bytes.extend_from_slice(&handwritten_map());
1508
1509        let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1510        assert_eq!(decoded.len(), 2);
1511        let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1512        assert_eq!(decoded[0], expected);
1513        assert_eq!(decoded[1], expected);
1514
1515        Ok(())
1516    }
1517
1518    /// An ignored key is not re-encoded, so the item's hash is the hash of what
1519    /// this version can represent and not of the bytes it decoded
1520    #[test]
1521    fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1522        let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1523        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1524        bytes.extend_from_slice(&[0x05, 0x07]);
1525
1526        let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1527        assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1528        assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1529        assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1530
1531        Ok(())
1532    }
1533
1534    /// Only integer keys are indexes. The spec rules out the HTTP header names
1535    /// as keys, so a key that is not an unsigned integer is not a future index
1536    /// to skip over
1537    #[test]
1538    fn non_integer_map_key_errors() {
1539        let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1540        text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1541        // key "5", unsigned integer value 7
1542        text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1543        assert!(matches!(
1544            RainMetaDocumentV1Item::cbor_decode(&with_prefix(&text_key)),
1545            Err(Error::SerdeCborError(_))
1546        ));
1547
1548        let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1549        negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1550        // key -1, unsigned integer value 7
1551        negative_key.extend_from_slice(&[0x20, 0x07]);
1552        assert!(matches!(
1553            RainMetaDocumentV1Item::cbor_decode(&with_prefix(&negative_key)),
1554            Err(Error::SerdeCborError(_))
1555        ));
1556    }
1557
1558    /// An unknown key is skipped, never counted as one of the mandatory keys,
1559    /// so a map carrying one instead of key 0 is still missing its payload and
1560    /// corrupts the document rather than decoding with the unknown key standing in.
1561    #[test]
1562    fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1563        let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1564        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1565        assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
1566    }
1567
1568    /// The bytes as a rain meta document: the document magic prefix, then
1569    /// whatever was handed in. The decoder requires the prefix, so a test
1570    /// that hand writes a cbor map wraps it here and stays about the map.
1571    fn with_prefix(bytes: &[u8]) -> Vec<u8> {
1572        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1573        document.extend_from_slice(bytes);
1574        document
1575    }
1576
1577    fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1578        RainMetaDocumentV1Item {
1579            payload: serde_bytes::ByteBuf::from(payload),
1580            magic,
1581            content_type: ContentType::None,
1582            content_encoding: ContentEncoding::None,
1583            content_language: ContentLanguage::None,
1584            schema: None,
1585        }
1586    }
1587
1588    // ---- helpers for the CAS / search tests ----
1589
1590    fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1591        let authoring_meta: AuthoringMeta = serde_json::from_str(
1592            r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1593        )
1594        .unwrap();
1595        let abi = authoring_meta.abi_encode_validate().unwrap();
1596        let item = RainMetaDocumentV1Item {
1597            payload: serde_bytes::ByteBuf::from(abi),
1598            magic: KnownMagic::AuthoringMetaV1,
1599            content_type: ContentType::Cbor,
1600            content_encoding: ContentEncoding::None,
1601            content_language: ContentLanguage::None,
1602            schema: None,
1603        };
1604        let doc =
1605            RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1606                .unwrap();
1607        (authoring_meta, doc)
1608    }
1609
1610    fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1611        RainMetaDocumentV1Item {
1612            payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1613            magic: KnownMagic::DotrainV1,
1614            content_type: ContentType::OctetStream,
1615            content_encoding: ContentEncoding::None,
1616            content_language: ContentLanguage::None,
1617            schema: None,
1618        }
1619    }
1620
1621    /// Handwritten canonical cbor for {0: h'01', 1: DotrainV1 magic}, written
1622    /// out byte by byte from the cbor spec, independent of cbor_encode.
1623    fn handwritten_map() -> Vec<u8> {
1624        vec![
1625            0xa2, // map(2)
1626            0x00, // key 0
1627            0x41, 0x01, // bytes(1) 0x01
1628            0x01, // key 1
1629            0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, // u64 DotrainV1
1630        ]
1631    }
1632
1633    /// hash(false) is keccak256 of the bare cbor map and hash(true) is
1634    /// keccak256 of the rain meta document prefix followed by the same map,
1635    /// pinned against independently handwritten bytes.
1636    #[test]
1637    fn test_hash_bare_vs_document() -> Result<(), Error> {
1638        let map_bytes = handwritten_map();
1639        let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1640        doc_bytes.extend_from_slice(&map_bytes);
1641
1642        let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1643        assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1644        assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1645        assert_ne!(item.hash(false)?, item.hash(true)?);
1646        Ok(())
1647    }
1648
1649    /// Empty input and a bare document prefix with no items are corrupt metas.
1650    #[test]
1651    fn test_cbor_decode_empty_is_corrupt() {
1652        // nothing at all is not a document - it does not begin with the magic
1653        assert!(matches!(
1654            RainMetaDocumentV1Item::cbor_decode(&[]),
1655            Err(Error::NotRainMetaDocument)
1656        ));
1657        // the prefix alone is a document carrying no meta, which is corrupt
1658        let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1659        assert!(matches!(
1660            RainMetaDocumentV1Item::cbor_decode(&prefix),
1661            Err(Error::CorruptMeta)
1662        ));
1663    }
1664
1665    /// metadata-v1.md: "Tooling that wishes to read meta MUST discard/ignore
1666    /// all binary data that does not begin with the magic number." A well
1667    /// formed item without the document prefix is exactly that data, and the
1668    /// document decoder refuses it as not a document - a distinct verdict from
1669    /// corrupt, since nothing about the bytes was read to reach it.
1670    #[test]
1671    fn test_cbor_decode_requires_the_document_prefix() {
1672        let bare = plain_item(KnownMagic::DotrainV1, vec![0x42])
1673            .cbor_encode()
1674            .unwrap();
1675        assert!(matches!(
1676            RainMetaDocumentV1Item::cbor_decode(&bare),
1677            Err(Error::NotRainMetaDocument)
1678        ));
1679        // the same bytes behind the prefix decode, so it is the prefix that
1680        // decides and not the item
1681        let items = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bare)).unwrap();
1682        assert_eq!(items.len(), 1);
1683        assert_eq!(items[0].payload.as_ref(), &[0x42]);
1684    }
1685
1686    /// Another magic number in the prefix position is not the document
1687    /// prefix, however well formed the rest is.
1688    #[test]
1689    fn test_cbor_decode_rejects_another_magic_as_the_prefix() {
1690        let bare = plain_item(KnownMagic::DotrainV1, vec![0x42])
1691            .cbor_encode()
1692            .unwrap();
1693        let mut document = KnownMagic::DotrainV1.to_prefix_bytes().to_vec();
1694        document.extend_from_slice(&bare);
1695        assert!(matches!(
1696            RainMetaDocumentV1Item::cbor_decode(&document),
1697            Err(Error::NotRainMetaDocument)
1698        ));
1699    }
1700
1701    /// Fewer bytes than the prefix is long cannot begin with it, including
1702    /// when those bytes are the prefix cut short.
1703    #[test]
1704    fn test_cbor_decode_rejects_data_shorter_than_the_prefix() {
1705        let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1706        for cut in 0..prefix.len() {
1707            assert!(
1708                matches!(
1709                    RainMetaDocumentV1Item::cbor_decode(&prefix[..cut]),
1710                    Err(Error::NotRainMetaDocument)
1711                ),
1712                "prefix cut to {} bytes",
1713                cut
1714            );
1715        }
1716    }
1717
1718    /// A valid map followed by truncated trailing bytes must not decode: the
1719    /// data does not end exactly at the last complete item.
1720    #[test]
1721    fn test_cbor_decode_trailing_truncated_is_corrupt() {
1722        let mut bytes = handwritten_map();
1723        bytes.push(0x1b); // u64 header with all 8 payload bytes missing
1724        assert!(matches!(
1725            RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)),
1726            Err(Error::CorruptMeta)
1727        ));
1728    }
1729
1730    /// Every way an item can run out of bytes is corrupt meta, not a serde
1731    /// cbor error: a truncated sole item, a map header promising entries the
1732    /// input does not carry, and a truncated item after a complete one.
1733    #[test]
1734    fn test_cbor_decode_truncated_item_is_corrupt() {
1735        let mut sole = handwritten_map();
1736        sole.pop();
1737        assert!(matches!(
1738            RainMetaDocumentV1Item::cbor_decode(&with_prefix(&sole)),
1739            Err(Error::CorruptMeta)
1740        ));
1741
1742        assert!(matches!(
1743            RainMetaDocumentV1Item::cbor_decode(&with_prefix(&[0xa2, 0x00, 0x41, 0x01])),
1744            Err(Error::CorruptMeta)
1745        ));
1746
1747        let mut after_complete = handwritten_map();
1748        after_complete.extend_from_slice(&[0xa2, 0x00]);
1749        assert!(matches!(
1750            RainMetaDocumentV1Item::cbor_decode(&with_prefix(&after_complete)),
1751            Err(Error::CorruptMeta)
1752        ));
1753    }
1754
1755    /// A valid map followed by a byte that is not valid cbor surfaces the
1756    /// serde cbor error.
1757    #[test]
1758    fn test_cbor_decode_trailing_garbage_errors() {
1759        let mut bytes = handwritten_map();
1760        bytes.push(0xff); // lone break byte
1761        assert!(matches!(
1762            RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)),
1763            Err(Error::SerdeCborError(_))
1764        ));
1765    }
1766
1767    /// A map without the mandatory payload key 0 corrupts the document, and
1768    /// takes an item beside it down too: one document is one emission from
1769    /// one emitter, and an emission carrying a broken claim is not mined for
1770    /// its readable parts.
1771    #[test]
1772    fn test_cbor_decode_missing_payload_is_corrupt() {
1773        let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; // {1: DotrainV1}
1774        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1775        assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
1776
1777        // beside a good item, the good item is not recovered
1778        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1779            .cbor_encode()
1780            .unwrap();
1781        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1782        document.extend_from_slice(&bytes);
1783        document.extend_from_slice(&good);
1784        assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1785    }
1786
1787    /// A map without the mandatory magic key 1, likewise.
1788    #[test]
1789    fn test_cbor_decode_missing_magic_is_corrupt() {
1790        let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; // {0: h'01'}
1791        assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
1792
1793        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1794            .cbor_encode()
1795            .unwrap();
1796        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1797        document.extend_from_slice(&bytes);
1798        document.extend_from_slice(&good);
1799        assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1800    }
1801
1802    /// A map carrying a magic number this version does not know is dropped,
1803    /// not an error: the magic is the format's extension point, and somebody
1804    /// else's well formed item travelling in the same document is not
1805    /// corruption. Alone it leaves nothing to return, which is CorruptMeta.
1806    #[test]
1807    fn test_cbor_decode_unknown_magic_is_dropped() {
1808        let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1809        bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1810        assert!(matches!(
1811            RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)),
1812            Err(Error::CorruptMeta)
1813        ));
1814    }
1815
1816    /// The unknown magic drop protects the items beside it: the foreign item
1817    /// first, so a decoder that stopped at it would return nothing, and the
1818    /// good item behind it still decodes.
1819    #[test]
1820    fn test_cbor_decode_drops_only_the_unknown_magic_item() {
1821        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1822            .cbor_encode()
1823            .unwrap();
1824
1825        let mut unknown_magic: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1826        unknown_magic.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1827
1828        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1829        document.extend_from_slice(&unknown_magic);
1830        document.extend_from_slice(&good);
1831
1832        let items = RainMetaDocumentV1Item::cbor_decode(&document).unwrap();
1833        assert_eq!(items.len(), 1, "expected only the good item");
1834        assert_eq!(items[0].magic, KnownMagic::DotrainV1);
1835        assert_eq!(items[0].payload.as_ref(), &[0x42]);
1836    }
1837
1838    /// The document prefix is not a magic number an item may carry: it is the
1839    /// first 8 bytes of a whole document. An item claiming it is malformed
1840    /// structure rather than a type this version does not read, so it stops
1841    /// the document instead of being dropped like an unknown number.
1842    #[test]
1843    fn test_cbor_decode_nested_document_magic_is_not_dropped() {
1844        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1845            .cbor_encode()
1846            .unwrap();
1847
1848        let mut nested: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1849        nested.extend_from_slice(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes());
1850
1851        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1852        document.extend_from_slice(&nested);
1853        document.extend_from_slice(&good);
1854
1855        assert!(matches!(
1856            RainMetaDocumentV1Item::cbor_decode(&document),
1857            Err(Error::SerdeCborError(_))
1858        ));
1859    }
1860
1861    /// A cbor map header plus the given already encoded key/value pairs,
1862    /// written per the cbor spec rather than through the encoder, so a map can
1863    /// carry a key twice which no encoder emits.
1864    fn handwritten_entries(entries: &[(Vec<u8>, Vec<u8>)]) -> Vec<u8> {
1865        assert!(entries.len() < 24);
1866        let mut bytes = vec![0xa0 | entries.len() as u8];
1867        for (key, value) in entries {
1868            bytes.extend_from_slice(key);
1869            bytes.extend_from_slice(value);
1870        }
1871        bytes
1872    }
1873
1874    /// cbor unsigned integer of the magic number, as a map key or a value.
1875    fn handwritten_magic(magic: KnownMagic) -> Vec<u8> {
1876        let mut bytes = vec![0x1b];
1877        bytes.extend_from_slice(&magic.to_prefix_bytes());
1878        bytes
1879    }
1880
1881    /// cbor text string of a string shorter than 24 bytes.
1882    fn handwritten_text(text: &str) -> Vec<u8> {
1883        assert!(text.len() < 24);
1884        let mut bytes = vec![0x60 | text.len() as u8];
1885        bytes.extend_from_slice(text.as_bytes());
1886        bytes
1887    }
1888
1889    /// The repro from #191: a map repeating key 0 must not decode with the
1890    /// last payload winning.
1891    #[test]
1892    fn test_cbor_decode_duplicate_payload_key_errors() {
1893        let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x00, 0x41, 0x02, 0x01, 0x1b];
1894        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1895        let error = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).unwrap_err();
1896        assert!(matches!(error, Error::SerdeCborError(_)));
1897        assert!(
1898            error.to_string().contains("duplicate field `payload`"),
1899            "{error}"
1900        );
1901    }
1902
1903    /// RFC 8949 ยง5.6: every recognised key is rejected when the map carries it
1904    /// twice, whether the repeated value differs from the first or matches it.
1905    #[test]
1906    fn test_cbor_decode_duplicate_any_key_errors() -> Result<(), Error> {
1907        let cases: [(Vec<u8>, Vec<u8>, Vec<u8>); 6] = [
1908            (vec![0x00], vec![0x41, 0x01], vec![0x41, 0x02]),
1909            (
1910                vec![0x01],
1911                handwritten_magic(KnownMagic::DotrainV1),
1912                handwritten_magic(KnownMagic::RainlangV1),
1913            ),
1914            (
1915                vec![0x02],
1916                handwritten_text("application/cbor"),
1917                handwritten_text("application/json"),
1918            ),
1919            (
1920                vec![0x03],
1921                handwritten_text("identity"),
1922                handwritten_text("deflate"),
1923            ),
1924            (vec![0x04], handwritten_text("en"), handwritten_text("none")),
1925            (
1926                handwritten_magic(KnownMagic::OaSchema),
1927                handwritten_text("hi"),
1928                handwritten_text("bye"),
1929            ),
1930        ];
1931        let base: Vec<(Vec<u8>, Vec<u8>)> = cases
1932            .iter()
1933            .map(|(key, value, _)| (key.clone(), value.clone()))
1934            .collect();
1935
1936        let decoded =
1937            RainMetaDocumentV1Item::cbor_decode(&with_prefix(&handwritten_entries(&base)))?;
1938        assert_eq!(decoded.len(), 1);
1939        assert_eq!(decoded[0].payload.as_ref(), &[0x01]);
1940        assert_eq!(decoded[0].magic, KnownMagic::DotrainV1);
1941        assert_eq!(decoded[0].content_type, ContentType::Cbor);
1942        assert_eq!(decoded[0].content_encoding, ContentEncoding::Identity);
1943        assert_eq!(decoded[0].content_language, ContentLanguage::En);
1944        assert_eq!(decoded[0].schema.as_deref(), Some("hi"));
1945
1946        for (key, value, other_value) in &cases {
1947            for repeated in [value, other_value] {
1948                let mut entries = base.clone();
1949                entries.push((key.clone(), repeated.clone()));
1950                let error = RainMetaDocumentV1Item::cbor_decode(&with_prefix(
1951                    &handwritten_entries(&entries),
1952                ))
1953                .unwrap_err();
1954                assert!(
1955                    matches!(error, Error::SerdeCborError(_)),
1956                    "{key:?} {error:?}"
1957                );
1958                assert!(
1959                    error.to_string().contains("duplicate field"),
1960                    "{key:?} {error}"
1961                );
1962            }
1963        }
1964        Ok(())
1965    }
1966
1967    /// An index this version has no meaning for is skipped rather than
1968    /// rejected, but RFC 8949 ยง5.6 does not ask whether a key is understood:
1969    /// a map that carries an unknown index twice is invalid too, whether that
1970    /// index is a plain integer or a magic number other than OaSchema.
1971    #[test]
1972    fn test_cbor_decode_duplicate_unknown_key_errors() -> Result<(), Error> {
1973        let base: Vec<(Vec<u8>, Vec<u8>)> = vec![
1974            (vec![0x00], vec![0x41, 0x01]),
1975            (vec![0x01], handwritten_magic(KnownMagic::DotrainV1)),
1976        ];
1977        // key 5 as a plausible future index and the OaHashList magic as a
1978        // future magic keyed entry, each with the value cbor to repeat it with
1979        let unknown: [(Vec<u8>, Vec<u8>, Vec<u8>); 2] = [
1980            (vec![0x05], vec![0x07], vec![0x08]),
1981            (
1982                handwritten_magic(KnownMagic::OaHashList),
1983                handwritten_text("hi"),
1984                handwritten_text("bye"),
1985            ),
1986        ];
1987
1988        for (key, value, other_value) in &unknown {
1989            let mut entries = base.clone();
1990            entries.push((key.clone(), value.clone()));
1991            let decoded =
1992                RainMetaDocumentV1Item::cbor_decode(&with_prefix(&handwritten_entries(&entries)))?;
1993            assert_eq!(decoded, vec![plain_item(KnownMagic::DotrainV1, vec![0x01])]);
1994
1995            for repeated in [value, other_value] {
1996                let mut duplicated = entries.clone();
1997                duplicated.push((key.clone(), repeated.clone()));
1998                let error = RainMetaDocumentV1Item::cbor_decode(&with_prefix(
1999                    &handwritten_entries(&duplicated),
2000                ))
2001                .unwrap_err();
2002                assert!(
2003                    matches!(error, Error::SerdeCborError(_)),
2004                    "{key:?} {error:?}"
2005                );
2006                assert!(
2007                    error.to_string().contains("duplicate map key"),
2008                    "{key:?} {error}"
2009                );
2010            }
2011        }
2012        Ok(())
2013    }
2014
2015    /// A handwritten item map carrying the rain meta document magic under key
2016    /// 1 decodes, so accepting the document magic as an item magic is the
2017    /// decoder's own behaviour and not an artefact of this crate's encoder.
2018    #[test]
2019    fn test_cbor_decode_handwritten_document_magic_item() {
2020        let bytes: Vec<u8> = vec![
2021            0xa2, // map(2)
2022            0x00, // key 0
2023            0x41, 0x01, // bytes(1) 0x01
2024            0x01, // key 1
2025            0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, // u64 RainMetaDocumentV1
2026        ];
2027        // The document magic in an item's magic position is structurally
2028        // invalid, so the meta carrying it does not decode.
2029        // rainlanguage/rain.metadata#204.
2030        assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
2031    }
2032
2033    /// The document magic as an item's own magic marks a payload that is
2034    /// itself a complete rain meta document, which
2035    /// `OrderBuilderStateV1::extract_from_meta` recurses into, so the codec
2036    /// must carry such an item in both directions and leave its payload byte
2037    /// for byte intact.
2038    #[test]
2039    fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
2040        let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
2041        let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
2042            &vec![inner.clone()],
2043            KnownMagic::RainMetaDocumentV1,
2044        )?;
2045        let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
2046        let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
2047            &vec![outer.clone()],
2048            KnownMagic::RainMetaDocumentV1,
2049        )?;
2050
2051        // Encoding can still write the document magic into an item's magic
2052        // position, and the payload really is a whole document. Decoding
2053        // refuses it anyway: a nested document is not a shape to descend into,
2054        // it is a corrupt meta, and the usable item inside does not rescue it.
2055        // rainlanguage/rain.metadata#204.
2056        assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
2057        assert_eq!(
2058            RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
2059            vec![inner]
2060        );
2061        Ok(())
2062    }
2063
2064    /// Nesting is not a leaf meta type: the unpack layer rejects the document
2065    /// magic so that no payload conversion is ever handed a whole document.
2066    #[test]
2067    fn test_document_magic_item_is_not_unpackable() {
2068        assert!(matches!(
2069            KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
2070            Err(Error::UnsupportedMeta)
2071        ));
2072        assert!(matches!(
2073            plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
2074            Err(Error::UnsupportedMeta)
2075        ));
2076    }
2077
2078    /// unpack decodes the payload according to the content encoding.
2079    #[test]
2080    fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
2081        let content = b"unpack me via deflate".to_vec();
2082        let packed = ContentEncoding::Deflate.encode(&content);
2083        assert_ne!(packed, content);
2084        let mut item = plain_item(KnownMagic::DotrainV1, packed);
2085        item.content_encoding = ContentEncoding::Deflate;
2086        assert_eq!(item.unpack()?, content);
2087
2088        let item = plain_item(KnownMagic::DotrainV1, content.clone());
2089        assert_eq!(item.unpack()?, content);
2090        Ok(())
2091    }
2092
2093    /// The 13 meta magics unpack; the document magic, the web data magic and
2094    /// the Oa magics are rejected with UnsupportedMeta.
2095    #[test]
2096    fn test_unpack_into_whitelist() {
2097        use strum::IntoEnumIterator;
2098        let supported = [
2099            KnownMagic::OpMetaV1,
2100            KnownMagic::DotrainV1,
2101            KnownMagic::RainlangV1,
2102            KnownMagic::SolidityAbiV2,
2103            KnownMagic::AuthoringMetaV1,
2104            KnownMagic::AuthoringMetaV2,
2105            KnownMagic::AddressList,
2106            KnownMagic::InterpreterCallerMetaV1,
2107            KnownMagic::ExpressionDeployerV2BytecodeV1,
2108            KnownMagic::DotrainSourceV1,
2109            KnownMagic::OrderBuilderStateV1,
2110            KnownMagic::RainlangSourceV1,
2111            KnownMagic::RaindexSignedContextOracleV1,
2112        ];
2113        for magic in supported {
2114            let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
2115            assert_eq!(unpacked, vec![0x61], "{:?}", magic);
2116        }
2117        let unsupported = [
2118            KnownMagic::RainMetaDocumentV1,
2119            KnownMagic::WebDataV1,
2120            KnownMagic::OaSchema,
2121            KnownMagic::OaHashList,
2122            KnownMagic::OaStructure,
2123            KnownMagic::OaTokenImage,
2124            KnownMagic::OaTokenCredentialLinks,
2125        ];
2126        for magic in unsupported {
2127            let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
2128            assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
2129        }
2130        // together the two lists cover every variant
2131        assert_eq!(
2132            supported.len() + unsupported.len(),
2133            KnownMagic::iter().count()
2134        );
2135    }
2136
2137    /// Invalid utf8 payloads error when unpacking into String rather than
2138    /// being replaced lossily.
2139    #[test]
2140    fn test_try_into_string_invalid_utf8_errors() {
2141        let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
2142        let result: Result<String, Error> = item.try_into();
2143        assert!(matches!(result, Err(Error::FromUtf8Error(_))));
2144    }
2145
2146    /// Unpacking into Vec<u8> decodes the content encoding first.
2147    #[test]
2148    fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
2149        let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
2150        let packed = ContentEncoding::Deflate.encode(&content);
2151        let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
2152        item.content_encoding = ContentEncoding::Deflate;
2153        let unpacked: Vec<u8> = item.try_into()?;
2154        assert_eq!(unpacked, content);
2155        assert_ne!(unpacked, packed);
2156        Ok(())
2157    }
2158
2159    /// Deflate encode produces a zlib stream (RFC1950 CMF byte 0x78) that is
2160    /// actually compressed and roundtrips through decode.
2161    #[test]
2162    fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
2163        let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
2164        let encoded = ContentEncoding::Deflate.encode(&content);
2165        assert_ne!(encoded, content);
2166        assert_eq!(encoded[0], 0x78);
2167        assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
2168        Ok(())
2169    }
2170
2171    /// None and Identity pass data through unchanged on encode and decode.
2172    #[test]
2173    fn test_content_encoding_passthrough() -> Result<(), Error> {
2174        let data = vec![0x00, 0xff, 0x10];
2175        for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
2176            assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
2177            assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
2178        }
2179        Ok(())
2180    }
2181
2182    /// Decode accepts a zlib stream and falls back to a raw deflate stream.
2183    /// Fixtures generated out of band from "hello rain deflate fixture".
2184    #[test]
2185    fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
2186        let content = b"hello rain deflate fixture".to_vec();
2187        let zlib: Vec<u8> = vec![
2188            120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
2189            73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
2190        ];
2191        let raw: Vec<u8> = vec![
2192            203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
2193            203, 172, 40, 41, 45, 74, 5, 0,
2194        ];
2195        assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
2196        assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
2197        Ok(())
2198    }
2199
2200    /// Data that is neither a zlib stream nor a raw deflate stream errors
2201    /// with InflateError instead of returning bytes.
2202    #[test]
2203    fn test_content_encoding_decode_garbage_errors() {
2204        let garbage = [0xffu8, 0xff, 0xff, 0xff];
2205        assert!(matches!(
2206            ContentEncoding::Deflate.decode(&garbage),
2207            Err(Error::InflateError(_))
2208        ));
2209    }
2210
2211    /// The CLI-facing strum names for the content headers are kebab-case.
2212    #[test]
2213    fn test_content_headers_strum_names() {
2214        use std::str::FromStr;
2215        assert_eq!(
2216            ContentEncoding::from_str("deflate").unwrap(),
2217            ContentEncoding::Deflate
2218        );
2219        assert_eq!(
2220            ContentEncoding::from_str("identity").unwrap(),
2221            ContentEncoding::Identity
2222        );
2223        assert_eq!(
2224            ContentEncoding::from_str("none").unwrap(),
2225            ContentEncoding::None
2226        );
2227        assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
2228        assert_eq!(
2229            ContentType::from_str("octet-stream").unwrap(),
2230            ContentType::OctetStream
2231        );
2232        assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
2233        assert_eq!(ContentType::Json.to_string(), "json");
2234        assert_eq!(
2235            ContentLanguage::from_str("en").unwrap(),
2236            ContentLanguage::En
2237        );
2238    }
2239
2240    /// Every documented meta magic maps to its KnownMeta while the document
2241    /// magic and the Oa magics are unsupported.
2242    #[test]
2243    fn test_known_meta_try_from_magic() {
2244        let cases: [(KnownMagic, KnownMeta); 13] = [
2245            (KnownMagic::OpMetaV1, KnownMeta::OpV1),
2246            (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
2247            (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
2248            (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
2249            (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
2250            (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
2251            (KnownMagic::AddressList, KnownMeta::AddressList),
2252            (
2253                KnownMagic::InterpreterCallerMetaV1,
2254                KnownMeta::InterpreterCallerMetaV1,
2255            ),
2256            (
2257                KnownMagic::ExpressionDeployerV2BytecodeV1,
2258                KnownMeta::ExpressionDeployerV2BytecodeV1,
2259            ),
2260            (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2261            (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2262            (
2263                KnownMagic::OrderBuilderStateV1,
2264                KnownMeta::OrderBuilderStateV1,
2265            ),
2266            (
2267                KnownMagic::RaindexSignedContextOracleV1,
2268                KnownMeta::RaindexSignedContextOracleV1,
2269            ),
2270        ];
2271        for (magic, meta) in cases {
2272            assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2273        }
2274        for magic in [
2275            KnownMagic::RainMetaDocumentV1,
2276            KnownMagic::WebDataV1,
2277            KnownMagic::OaSchema,
2278            KnownMagic::OaHashList,
2279            KnownMagic::OaStructure,
2280            KnownMagic::OaTokenImage,
2281            KnownMagic::OaTokenCredentialLinks,
2282        ] {
2283            assert!(
2284                matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2285                "{:?}",
2286                magic
2287            );
2288        }
2289    }
2290
2291    /// KnownMeta parses from and displays as the kebab-case names used by the
2292    /// CLI (validate --meta, build, schema show).
2293    #[test]
2294    fn test_known_meta_strum_parse_display() {
2295        use std::str::FromStr;
2296        assert_eq!(
2297            KnownMeta::from_str("solidity-abi-v2").unwrap(),
2298            KnownMeta::SolidityAbiV2
2299        );
2300        assert_eq!(
2301            KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2302            KnownMeta::InterpreterCallerMetaV1
2303        );
2304        assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2305    }
2306
2307    /// search() lowercases the hash before building the query variables.
2308    #[tokio::test]
2309    async fn test_search_lowercases_hash() {
2310        use httpmock::prelude::*;
2311        let (_, doc) = sample_authoring_doc();
2312        let hash_upper = format!("0x{}", "AB".repeat(32));
2313        let server = MockServer::start();
2314        let mock = server.mock(|when, then| {
2315            when.method(POST)
2316                .body_contains(hash_upper.to_ascii_lowercase());
2317            then.status(200).json_body(json!({
2318                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2319            }));
2320        });
2321        let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2322        assert_eq!(response.bytes, doc);
2323        mock.assert();
2324    }
2325
2326    /// search() queries every subgraph and the first success wins even when
2327    /// an earlier subgraph fails.
2328    #[tokio::test]
2329    async fn test_search_first_success_wins() {
2330        use httpmock::prelude::*;
2331        let (_, doc) = sample_authoring_doc();
2332        let bad = MockServer::start();
2333        let _bad_mock = bad.mock(|when, then| {
2334            when.method(POST);
2335            then.status(500).body("subgraph down");
2336        });
2337        let good = MockServer::start();
2338        let _good_mock = good.mock(|when, then| {
2339            when.method(POST);
2340            then.status(200).json_body(json!({
2341                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2342            }));
2343        });
2344        let response = search(
2345            &format!("0x{}", "11".repeat(32)),
2346            &vec![bad.url("/sg"), good.url("/sg")],
2347        )
2348        .await
2349        .unwrap();
2350        assert_eq!(response.bytes, doc);
2351    }
2352
2353    /// An empty subgraph list has nothing to fan out to, so the search
2354    /// reports a miss rather than reaching futures::select_ok, which panics on
2355    /// an empty iterator.
2356    #[tokio::test]
2357    async fn test_search_empty_subgraphs_is_a_miss() {
2358        let hash = format!("0x{}", "33".repeat(32));
2359        assert!(matches!(
2360            search(&hash, &vec![]).await,
2361            Err(Error::NoRecordFound)
2362        ));
2363    }
2364
2365    /// The erc165 gate short circuits: neither answer reaches the
2366    /// IDescribedByMetaV1 supportsInterface call, so the queued "true" is
2367    /// never consumed and cannot be mistaken for the contract's own answer.
2368    ///
2369    /// The two answers are not the same fact. A contract that says no is
2370    /// `Ok(false)`; a probe that could not finish is an error, because a
2371    /// transport failure is not a contract declining an interface. rain-erc
2372    /// makes that distinction itself - "callers can treat that as answer
2373    /// unknown rather than silently reading no support" - and an
2374    /// `unwrap_or(false)` here threw it away.
2375    #[tokio::test]
2376    async fn test_implements_erc165_gate_short_circuits() {
2377        let address = Address::random();
2378
2379        // erc165 check1 answers false
2380        let asserter = Asserter::new();
2381        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2382        asserter
2383            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2384        asserter
2385            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2386        assert!(!implements_i_described_by_meta_v1(&provider, address)
2387            .await
2388            .unwrap());
2389
2390        // erc165 probe errors. Getting an error back is itself the proof of
2391        // the short circuit: had the queued "true" been consumed by the
2392        // interface probe, this would be Ok(true).
2393        let asserter = Asserter::new();
2394        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2395        asserter.push_failure(ErrorPayload {
2396            code: -32000,
2397            message: "connection reset".into(),
2398            data: None,
2399        });
2400        asserter
2401            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2402        assert!(implements_i_described_by_meta_v1(&provider, address)
2403            .await
2404            .is_err());
2405    }
2406
2407    /// An empty eth_call response is a contract that did not answer, which
2408    /// ERC-165 reads as "does not implement".
2409    #[tokio::test]
2410    async fn test_implements_empty_response_is_false() {
2411        let address = Address::random();
2412        let asserter = Asserter::new();
2413        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2414        asserter
2415            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2416        asserter
2417            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2418        asserter.push_success(&"0x");
2419        assert!(!implements_i_described_by_meta_v1(&provider, address)
2420            .await
2421            .unwrap());
2422    }
2423
2424    /// A non-revert failure of the IDescribedByMetaV1 supportsInterface call is
2425    /// "answer unknown": it propagates as Err rather than reading as "does not
2426    /// implement".
2427    #[tokio::test]
2428    async fn test_implements_described_by_call_error_is_unknown() {
2429        let address = Address::random();
2430        let asserter = Asserter::new();
2431        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2432        asserter
2433            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2434        asserter
2435            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2436        asserter.push_failure(ErrorPayload {
2437            code: -32005,
2438            message: "rate limit exceeded".into(),
2439            data: None,
2440        });
2441        let error = implements_i_described_by_meta_v1(&provider, address)
2442            .await
2443            .unwrap_err();
2444        assert!(error.to_string().contains("rate limit exceeded"));
2445    }
2446
2447    /// A non-empty IDescribedByMetaV1 supportsInterface response that does not
2448    /// decode as bool is a decode failure, so "answer unknown" rather than
2449    /// "does not implement".
2450    #[tokio::test]
2451    async fn test_implements_undecodable_response_is_unknown() {
2452        let address = Address::random();
2453        let asserter = Asserter::new();
2454        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2455        asserter
2456            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2457        asserter
2458            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2459        asserter.push_success(&"0xdeadbeef");
2460        implements_i_described_by_meta_v1(&provider, address)
2461            .await
2462            .unwrap_err();
2463    }
2464
2465    /// No constructor injects a subgraph the caller did not ask for, and a
2466    /// store with none resolves every network lookup to None rather than
2467    /// reaching the select_ok panic.
2468    #[tokio::test]
2469    async fn test_store_constructors_inject_no_subgraphs() {
2470        assert!(Store::new().subgraphs().is_empty());
2471        assert!(Store::default().subgraphs().is_empty());
2472        assert!(
2473            Store::create(&vec![], &MetaCache::default(), &HashMap::new())
2474                .subgraphs()
2475                .is_empty()
2476        );
2477
2478        let hash = [0u8; 32];
2479        let mut store = Store::default();
2480        assert!(store.update(&hash).await.is_err());
2481    }
2482
2483    /// create() takes only the given subgraphs, and keeps a dotrain uri only
2484    /// when its hash is present in the cache.
2485    ///
2486    /// This used to assert create() dropped a cache entry whose bytes did not
2487    /// hash to its key. That entry is no longer constructible: create() takes
2488    /// a [MetaCache], which has no way to hold one, so there is nothing left
2489    /// for create() to validate.
2490    #[test]
2491    fn test_store_create_validates_entries() {
2492        let (_, doc) = sample_authoring_doc();
2493        let good_hash = keccak256(&doc).0.to_vec();
2494        let mut cache = MetaCache::default();
2495        cache.insert_verified(&good_hash, doc.clone()).unwrap();
2496        let mut dotrain_cache = HashMap::new();
2497        dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2498        dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2499
2500        let store = Store::create(
2501            &vec!["https://example.com/custom-sg".to_string()],
2502            &cache,
2503            &dotrain_cache,
2504        );
2505
2506        assert_eq!(
2507            store.subgraphs(),
2508            &vec!["https://example.com/custom-sg".to_string()]
2509        );
2510        assert_eq!(store.get_meta(&good_hash), Some(&doc));
2511        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2512        assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2513    }
2514
2515    /// add_subgraphs skips urls already present.
2516    #[test]
2517    fn test_store_add_subgraphs_dedupe() {
2518        let mut store = Store::new();
2519        store.add_subgraphs(&vec!["sg-a".to_string()]);
2520        store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2521        assert_eq!(
2522            store.subgraphs(),
2523            &vec!["sg-a".to_string(), "sg-b".to_string()]
2524        );
2525    }
2526
2527    /// set_dotrain on a fresh uri returns (new_hash, empty), keyed by the
2528    /// keccak of the cbor encoded DotrainV1 meta item, and every dotrain
2529    /// getter resolves it.
2530    #[test]
2531    fn test_store_dotrain_getters_and_set_fresh() {
2532        let mut store = Store::new();
2533        let text = "some dotrain content";
2534        let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2535        assert!(old.is_empty());
2536        let expected_item = RainMetaDocumentV1Item {
2537            payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2538            magic: KnownMagic::DotrainV1,
2539            content_type: ContentType::OctetStream,
2540            content_encoding: ContentEncoding::None,
2541            content_language: ContentLanguage::None,
2542            schema: None,
2543        };
2544        let expected_bytes = expected_item.cbor_encode().unwrap();
2545        assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2546        assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2547        assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2548        assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2549        assert_eq!(store.get_dotrain_hash("other.rain"), None);
2550        assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2551        assert_eq!(store.get_dotrain_meta("other.rain"), None);
2552    }
2553
2554    /// set_dotrain branches: same content keeps the meta and reports no old
2555    /// hash; different content remaps the uri and drops or keeps the old
2556    /// meta per keep_old.
2557    #[test]
2558    fn test_store_set_dotrain_branches() {
2559        let mut store = Store::new();
2560        let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2561
2562        // same content again: same hash, no old hash, meta retained
2563        let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2564        assert_eq!(hash_same, hash_one);
2565        assert!(old_same.is_empty());
2566        assert!(store.get_meta(&hash_one).is_some());
2567
2568        // different content, keep_old = false: remap and drop the old meta
2569        let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2570        assert_ne!(hash_two, hash_one);
2571        assert_eq!(old_two, hash_one);
2572        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2573        assert!(store.get_meta(&hash_one).is_none());
2574        assert!(store.get_meta(&hash_two).is_some());
2575
2576        // different content, keep_old = true: old meta kept
2577        let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2578        assert_eq!(old_three, hash_two);
2579        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2580        assert!(store.get_meta(&hash_two).is_some());
2581        assert!(store.get_meta(&hash_three).is_some());
2582    }
2583
2584    /// delete_dotrain removes the uri mapping and honors keep_meta for the
2585    /// cached meta bytes.
2586    #[test]
2587    fn test_store_delete_dotrain_keep_meta() {
2588        let mut store = Store::new();
2589        let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2590        store.delete_dotrain("d.rain", false);
2591        assert_eq!(store.get_dotrain_hash("d.rain"), None);
2592        assert!(store.get_meta(&hash).is_none());
2593
2594        let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2595        store.delete_dotrain("d.rain", true);
2596        assert_eq!(store.get_dotrain_hash("d.rain"), None);
2597        assert!(store.get_meta(&hash_again).is_some());
2598    }
2599
2600    /// two uris holding identical text share one cache entry, so dropping one
2601    /// of them must leave the other's meta resolvable, and only the last uri
2602    /// off the hash takes the meta with it.
2603    #[test]
2604    fn test_store_dotrain_shared_meta_survives_delete() {
2605        let mut store = Store::new();
2606        let (hash, _) = store.set_dotrain("same text", "a.rain", false).unwrap();
2607        let (hash_b, _) = store.set_dotrain("same text", "b.rain", false).unwrap();
2608        assert_eq!(hash_b, hash);
2609
2610        store.delete_dotrain("a.rain", false);
2611        assert_eq!(store.get_dotrain_hash("a.rain"), None);
2612        assert_eq!(store.get_dotrain_hash("b.rain"), Some(&hash));
2613        assert!(store.get_dotrain_meta("b.rain").is_some());
2614
2615        store.delete_dotrain("b.rain", false);
2616        assert!(store.get_meta(&hash).is_none());
2617    }
2618
2619    /// remapping one uri off a shared hash with keep_old = false must not take
2620    /// the meta the other uri still maps to.
2621    #[test]
2622    fn test_store_set_dotrain_keeps_shared_old_meta() {
2623        let mut store = Store::new();
2624        let (shared, _) = store.set_dotrain("same text", "a.rain", false).unwrap();
2625        store.set_dotrain("same text", "b.rain", false).unwrap();
2626
2627        let (new_hash, old_hash) = store.set_dotrain("other text", "a.rain", false).unwrap();
2628        assert_eq!(old_hash, shared);
2629        assert_ne!(new_hash, shared);
2630        assert!(store.get_dotrain_meta("b.rain").is_some());
2631
2632        store.set_dotrain("other text", "b.rain", false).unwrap();
2633        assert!(store.get_meta(&shared).is_none());
2634    }
2635
2636    /// merge keeps this store's entry in every map on a key collision, takes
2637    /// the keys it does not already hold, and unions the subgraphs.
2638    #[test]
2639    fn test_store_merge_semantics() {
2640        let mut ours = Store::new();
2641        let mut theirs = Store::new();
2642
2643        // meta cache: two different metas cannot share a key - the key IS
2644        // their digest - so merge takes the other store's entry rather than
2645        // choosing between them
2646        let mine = b"mine".to_vec();
2647        let yours = b"yours".to_vec();
2648        let mine_hash = keccak256(&mine).0.to_vec();
2649        let yours_hash = keccak256(&yours).0.to_vec();
2650        ours.update_with(&mine_hash, &mine).unwrap();
2651        theirs.update_with(&yours_hash, &yours).unwrap();
2652
2653        // same dotrain uri, different content
2654        let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2655        let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2656
2657        theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2658
2659        ours.merge(&theirs);
2660
2661        assert_eq!(ours.get_meta(&mine_hash), Some(&mine));
2662        assert_eq!(ours.get_meta(&yours_hash), Some(&yours));
2663        // dotrain: existing uri mapping wins
2664        assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2665        // subgraphs merged
2666        assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2667    }
2668
2669    /// update() stores the fetched bytes under the requested hash and each
2670    /// inner meta item under the keccak of its own encoding; update_check
2671    /// serves a cached hash without any network access.
2672    #[tokio::test]
2673    async fn test_store_update_and_update_check() {
2674        use httpmock::prelude::*;
2675        let authoring_meta: AuthoringMeta = serde_json::from_str(
2676            r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2677        )
2678        .unwrap();
2679        let item_one = RainMetaDocumentV1Item {
2680            payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2681            magic: KnownMagic::AuthoringMetaV1,
2682            content_type: ContentType::Cbor,
2683            content_encoding: ContentEncoding::None,
2684            content_language: ContentLanguage::None,
2685            schema: None,
2686        };
2687        let item_two = sample_dotrain_item();
2688        let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2689            &vec![item_one.clone(), item_two.clone()],
2690            KnownMagic::RainMetaDocumentV1,
2691        )
2692        .unwrap();
2693        let requested = keccak256(&doc).0.to_vec();
2694        let server = MockServer::start();
2695        let _mock = server.mock(|when, then| {
2696            when.method(POST);
2697            then.status(200).json_body(json!({
2698                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2699            }));
2700        });
2701        let mut store = Store::new();
2702        store.add_subgraphs(&vec![server.url("/sg")]);
2703        let fetched = store.update(&requested).await.cloned().unwrap();
2704        assert_eq!(fetched, doc);
2705        assert_eq!(store.get_meta(&requested), Some(&doc));
2706        let inner_one = item_one.cbor_encode().unwrap();
2707        let inner_two = item_two.cbor_encode().unwrap();
2708        assert_eq!(
2709            store.get_meta(keccak256(&inner_one).0.as_ref()),
2710            Some(&inner_one)
2711        );
2712        assert_eq!(
2713            store.get_meta(keccak256(&inner_two).0.as_ref()),
2714            Some(&inner_two)
2715        );
2716
2717        // update_check: cached hash short-circuits, no subgraphs needed
2718        let mut cached_store = Store::new();
2719        let bytes = b"standalone meta bytes".to_vec();
2720        let hash = keccak256(&bytes).0.to_vec();
2721        assert!(cached_store.update_with(&hash, &bytes).is_ok());
2722        assert_eq!(cached_store.update_check(&hash).await.unwrap(), &bytes);
2723    }
2724
2725    /// update() applies the same keccak gate as update_with to the subgraph
2726    /// response, so bytes that do not hash to the requested hash poison
2727    /// neither the requested key nor the inner item keys.
2728    #[tokio::test]
2729    async fn test_store_update_rejects_hash_mismatch() {
2730        use httpmock::prelude::*;
2731        let (_, doc) = sample_authoring_doc();
2732        let requested = keccak256(b"the real content").0.to_vec();
2733        let server = MockServer::start();
2734        let _mock = server.mock(|when, then| {
2735            when.method(POST);
2736            then.status(200).json_body(json!({
2737                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2738            }));
2739        });
2740        let mut store = Store::new();
2741        store.add_subgraphs(&vec![server.url("/sg")]);
2742        assert!(store.update(&requested).await.is_err());
2743        assert!(store.get_meta(&requested).is_none());
2744        assert!(store.cache().is_empty());
2745        // the miss is not cached either, so update_check retries and misses again
2746        assert!(store.update_check(&requested).await.is_err());
2747    }
2748
2749    /// Store::new() starts with no subgraphs, so every uncached lookup that
2750    /// reaches the network on it resolves to None instead of panicking.
2751    #[tokio::test]
2752    async fn test_store_no_subgraphs_lookups_return_none() {
2753        let hash = [0u8; 32];
2754        let mut store = Store::new();
2755        assert!(store.update(&hash).await.is_err());
2756        assert!(store.update_check(&hash).await.is_err());
2757        assert!(store.cache().is_empty());
2758    }
2759
2760    /// update_with enforces keccak(bytes) == hash, leaves an existing entry
2761    /// untouched, and unpacks inner items only for RainMetaDocumentV1
2762    /// prefixed bytes.
2763    #[test]
2764    fn test_store_update_with_validation_and_content() {
2765        // hash mismatch rejected
2766        let mut store = Store::new();
2767        let bytes = b"payload bytes".to_vec();
2768        let wrong_hash = vec![0x99u8; 32];
2769        // A mismatch is CorruptRecord, not NoRecordFound: the responder
2770        // answered about one hash with bytes that are another, which is not
2771        // the same fact as the hash being absent. #234 and #213 settled that
2772        // distinction for the query layer.
2773        match store.update_with(&wrong_hash, &bytes).unwrap_err() {
2774            Error::CorruptRecord(message) => {
2775                assert!(
2776                    message.contains(&hex::encode_prefixed(&wrong_hash)),
2777                    "{}",
2778                    message
2779                )
2780            }
2781            other => panic!("expected CorruptRecord, got {:?}", other),
2782        }
2783        assert!(store.get_meta(&wrong_hash).is_none());
2784        // valid pair stored
2785        let hash = keccak256(&bytes).0.to_vec();
2786        assert_eq!(store.update_with(&hash, &bytes).unwrap(), &bytes);
2787
2788        // an already cached key returns its entry rather than inserting again.
2789        // Note what is no longer expressible here: the old version of this
2790        // block seeded one hash with unrelated bytes and asserted a later write
2791        // did not overwrite them. Different bytes cannot share a key when the
2792        // key is their digest, so "overwritten with something else" is not a
2793        // state [MetaCache] can be in.
2794        let mut seeded = Store::new();
2795        let planted = b"planted value".to_vec();
2796        let planted_hash = keccak256(&planted).0.to_vec();
2797        seeded.update_with(&planted_hash, &planted).unwrap();
2798        assert_eq!(seeded.cache().len(), 1);
2799        assert_eq!(
2800            seeded.update_with(&planted_hash, &planted).unwrap(),
2801            &planted
2802        );
2803        assert_eq!(seeded.cache().len(), 1);
2804
2805        // prefixed document: inner item stored under keccak of its encoding
2806        let (_, doc) = sample_authoring_doc();
2807        let doc_hash = keccak256(&doc).0.to_vec();
2808        let mut doc_store = Store::new();
2809        assert!(doc_store.update_with(&doc_hash, &doc).is_ok());
2810        let inner = doc[8..].to_vec();
2811        assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2812
2813        // bare cbor sequence without the document prefix: no inner extraction
2814        let item_a = sample_dotrain_item().cbor_encode().unwrap();
2815        let (_, doc_b) = sample_authoring_doc();
2816        let item_b = doc_b[8..].to_vec();
2817        let seq = [item_a.clone(), item_b].concat();
2818        let seq_hash = keccak256(&seq).0.to_vec();
2819        let mut seq_store = Store::new();
2820        assert!(seq_store.update_with(&seq_hash, &seq).is_ok());
2821        assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2822    }
2823
2824    fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2825        store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2826    }
2827
2828    /// bytes32_to_str propagates invalid utf8 as an error instead of
2829    /// swallowing it.
2830    #[test]
2831    fn test_bytes32_to_str_invalid_utf8() {
2832        let mut bytes = [0u8; 32];
2833        bytes[0] = 0xf0;
2834        bytes[1] = 0x28;
2835        bytes[2] = 0x8c;
2836        bytes[3] = 0x28;
2837        assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2838        let no_nul = [0xffu8; 32];
2839        assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2840    }
2841}