1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use std::{collections::HashMap, io::Cursor, sync::Arc};

use anyhow::{anyhow, Result};
use async_std::sync::Mutex;
use async_trait::async_trait;
use cid::{
    multihash::{Code, MultihashDigest},
    Cid,
};
use libipld_core::{
    codec::{Codec, Decode, Encode},
    ipld::Ipld,
    raw::RawCodec,
};

#[cfg(not(target_arch = "wasm32"))]
pub trait UcanStoreConditionalSend: Send {}

#[cfg(not(target_arch = "wasm32"))]
impl<U> UcanStoreConditionalSend for U where U: Send {}

#[cfg(target_arch = "wasm32")]
pub trait UcanStoreConditionalSend {}

#[cfg(target_arch = "wasm32")]
impl<U> UcanStoreConditionalSend for U {}

#[cfg(not(target_arch = "wasm32"))]
pub trait UcanStoreConditionalSendSync: Send + Sync {}

#[cfg(not(target_arch = "wasm32"))]
impl<U> UcanStoreConditionalSendSync for U where U: Send + Sync {}

#[cfg(target_arch = "wasm32")]
pub trait UcanStoreConditionalSendSync {}

#[cfg(target_arch = "wasm32")]
impl<U> UcanStoreConditionalSendSync for U {}

/// This trait is meant to be implemented by a storage backend suitable for
/// persisting UCAN tokens that may be referenced as proofs by other UCANs
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait UcanStore<C: Codec + Default = RawCodec>: UcanStoreConditionalSendSync {
    /// Read a value from the store by CID, returning a Result<Option<...>> that unwraps
    /// to None if no value is found, otherwise Some
    async fn read<T: Decode<C>>(&self, cid: &Cid) -> Result<Option<T>>;

    /// Write a value to the store, receiving a Result that wraps the values CID if the
    /// write was successful
    async fn write<T: Encode<C> + UcanStoreConditionalSend + core::fmt::Debug>(
        &mut self,
        token: T,
    ) -> Result<Cid>;
}

/// This trait is sugar over the UcanStore trait to add convenience methods
/// for the case of storing JWT-encoded UCAN strings using the 'raw' codec
/// which is the only combination strictly required by the UCAN spec
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait UcanJwtStore: UcanStore<RawCodec> {
    async fn require_token(&self, cid: &Cid) -> Result<String> {
        match self.read_token(cid).await? {
            Some(token) => Ok(token),
            None => Err(anyhow!("No token found for CID {}", cid.to_string())),
        }
    }

    async fn read_token(&self, cid: &Cid) -> Result<Option<String>> {
        let codec = RawCodec::default();

        if cid.codec() != u64::from(codec) {
            return Err(anyhow!(
                "Only 'raw' codec supported, but CID refers to {:#x}",
                cid.codec()
            ));
        }

        match self.read::<Ipld>(cid).await? {
            Some(Ipld::Bytes(bytes)) => Ok(Some(std::str::from_utf8(&bytes)?.to_string())),
            _ => Err(anyhow!("No UCAN was found for CID {:?}", cid)),
        }
    }

    async fn write_token(&mut self, token: &str) -> Result<Cid> {
        self.write(Ipld::Bytes(token.as_bytes().to_vec())).await
    }
}

impl<U> UcanJwtStore for U where U: UcanStore<RawCodec> {}

/// A basic in-memory store that implements UcanStore for the 'raw'
/// codec. This will serve for basic use cases and tests, but it is
/// recommended that a store that persists to disk be used in most
/// practical use cases.
#[derive(Clone, Default, Debug)]
pub struct MemoryStore {
    dags: Arc<Mutex<HashMap<Cid, Vec<u8>>>>,
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl UcanStore<RawCodec> for MemoryStore {
    async fn read<T: Decode<RawCodec>>(&self, cid: &Cid) -> Result<Option<T>> {
        let codec = RawCodec::default();
        let dags = self.dags.lock().await;

        Ok(match dags.get(cid) {
            Some(bytes) => Some(T::decode(codec, &mut Cursor::new(bytes))?),
            None => None,
        })
    }

    async fn write<T: Encode<RawCodec> + UcanStoreConditionalSend + core::fmt::Debug>(
        &mut self,
        token: T,
    ) -> Result<Cid> {
        let codec = RawCodec::default();
        let block = codec.encode(&token)?;
        let cid = Cid::new_v1(codec.into(), Code::Blake2b256.digest(&block));

        let mut dags = self.dags.lock().await;
        dags.insert(cid.clone(), block);

        Ok(cid)
    }
}