sn_api/app/
register.rs

1// Copyright 2023 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9pub use sn_interface::types::register::{Entry, EntryHash};
10
11use crate::safeurl::{ContentType, SafeUrl, XorUrl};
12use crate::{Error, Result, Safe};
13
14use sn_client::Error as ClientError;
15use sn_interface::{
16    messaging::data::Error as ErrorMsg,
17    types::{
18        register::{Permissions, Policy, User},
19        DataAddress, Error as SafeNdError, RegisterAddress,
20    },
21};
22
23use rand::Rng;
24use std::collections::{BTreeMap, BTreeSet};
25use tracing::{debug, info};
26use xor_name::XorName;
27
28impl Safe {
29    // === Register data operations ===
30    /// Create a Register on the network
31    pub async fn register_create(
32        &self,
33        name: Option<XorName>,
34        tag: u64,
35        content_type: ContentType,
36    ) -> Result<XorUrl> {
37        debug!(
38            "Storing Register data with tag type: {}, xorname: {:?}, dry_run: {}",
39            tag, name, self.dry_run_mode
40        );
41
42        let xorname = name.unwrap_or_else(xor_name::rand::random);
43        info!("Xorname for new Register storage: {:?}", &xorname);
44
45        let xorurl = SafeUrl::from_register(xorname, tag, content_type)?.encode(self.xorurl_base);
46
47        // return early if dry_run_mode
48        if self.dry_run_mode {
49            return Ok(xorurl);
50        }
51
52        // The Register's owner will be the client's public key
53        let client = self.get_safe_client()?;
54        let owner = User::Key(client.public_key());
55
56        // Store the Register on the network
57        let (_, op_batch) = client
58            .create_register(xorname, tag, policy(owner))
59            .await
60            .map_err(|e| {
61                Error::NetDataError(format!("Failed to prepare store Register operation: {e:?}",))
62            })?;
63
64        client.publish_register_ops(op_batch).await?;
65
66        Ok(xorurl)
67    }
68
69    /// Read value from a Register on the network
70    pub async fn register_read(&self, url: &str) -> Result<BTreeSet<(EntryHash, Entry)>> {
71        debug!("Getting Register data from: {:?}", url);
72        let safeurl = self.parse_and_resolve_url(url).await?;
73
74        self.register_fetch_entries(&safeurl).await
75    }
76
77    /// Read value from a Register on the network by its hash
78    pub async fn register_read_entry(&self, url: &str, hash: EntryHash) -> Result<Entry> {
79        debug!("Getting Register data from: {:?}", url);
80        let safeurl = self.parse_and_resolve_url(url).await?;
81
82        self.register_fetch_entry(&safeurl, hash).await
83    }
84
85    /// Fetch a Register from a `SafeUrl` without performing any type of URL resolution
86    /// Supports version hashes:
87    /// e.g. safe://mysafeurl?v=ce56a3504c8f27bfeb13bdf9051c2e91409230ea
88    pub(crate) async fn register_fetch_entries(
89        &self,
90        url: &SafeUrl,
91    ) -> Result<BTreeSet<(EntryHash, Entry)>> {
92        debug!("Fetching Register entries from {}", url);
93        let result = match url.content_version() {
94            Some(v) => {
95                let hash = v.entry_hash();
96                debug!("Take entry with version hash: {:?}", hash);
97                self.register_fetch_entry(url, hash)
98                    .await
99                    .map(|entry| vec![(hash, entry)].into_iter().collect())
100            }
101            None => {
102                debug!("No version so take latest entry from Register at: {}", url);
103                let address = self.get_register_address(url)?;
104                let client = self.get_safe_client()?;
105                match client.read_register(address).await {
106                    Ok(entry) => Ok(entry),
107                    Err(ClientError::NetworkDataError(SafeNdError::NoSuchEntry(_))) => Err(
108                        Error::EmptyContent(format!("Empty Register found at \"{url}\"")),
109                    ),
110                    Err(ClientError::ErrorMsg {
111                        source: ErrorMsg::AccessDenied(_),
112                        ..
113                    }) => Err(Error::AccessDenied(format!(
114                        "Couldn't read entry from Register found at \"{url}\"",
115                    ))),
116                    Err(err) => Err(Error::NetDataError(format!(
117                        "Failed to read latest value from Register data: {err:?}",
118                    ))),
119                }
120            }
121        };
122
123        match result {
124            Ok(data) => {
125                debug!("Register retrieved from {}...", url);
126                Ok(data)
127            }
128            Err(Error::EmptyContent(_)) => Err(Error::EmptyContent(format!(
129                "Register found at \"{url}\" was empty",
130            ))),
131            Err(Error::ContentNotFound(_)) => Err(Error::ContentNotFound(format!(
132                "No Register found at \"{url}\"",
133            ))),
134            other_err => other_err,
135        }
136    }
137
138    /// Fetch a Register from a `SafeUrl` without performing any type of URL resolution
139    pub(crate) async fn register_fetch_entry(
140        &self,
141        url: &SafeUrl,
142        hash: EntryHash,
143    ) -> Result<Entry> {
144        // TODO: allow to specify the hash with the SafeUrl as well: safeurl.content_hash(),
145        // e.g. safe://mysafeurl#ce56a3504c8f27bfeb13bdf9051c2e91409230ea
146        let address = self.get_register_address(url)?;
147        let client = self.get_safe_client()?;
148        client
149            .get_register_entry(address, hash)
150            .await
151            .map_err(|err| {
152                if let ClientError::ErrorMsg {
153                    source: sn_interface::messaging::data::Error::NoSuchEntry(_),
154                    ..
155                } = err
156                {
157                    Error::HashNotFound(hash)
158                } else {
159                    Error::NetDataError(format!(
160                        "Failed to retrieve entry with hash '{}' from Register data: {err:?}",
161                        hex::encode(hash.0),
162                    ))
163                }
164            })
165    }
166
167    /// Write value to a Register on the network
168    pub async fn register_write(
169        &self,
170        url: &str,
171        entry: Entry,
172        parents: BTreeSet<EntryHash>,
173    ) -> Result<EntryHash> {
174        let reg_url = self.parse_and_resolve_url(url).await?;
175        let address = self.get_register_address(&reg_url)?;
176        if self.dry_run_mode {
177            return Ok(EntryHash(rand::thread_rng().gen::<[u8; 32]>()));
178        }
179
180        let client = self.get_safe_client()?;
181        let (entry_hash, op_batch) = match client
182            .write_to_local_register(address, entry, parents)
183            .await
184        {
185            Ok(data) => data,
186            Err(
187                ClientError::NetworkDataError(SafeNdError::AccessDenied(_))
188                | ClientError::ErrorMsg {
189                    source: ErrorMsg::AccessDenied(_),
190                    ..
191                },
192            ) => {
193                return Err(Error::AccessDenied(format!(
194                    "Couldn't write data on Register found at \"{url}\"",
195                )));
196            }
197            Err(err) => {
198                return Err(Error::NetDataError(format!(
199                    "Failed to write data on Register: {err:?}"
200                )));
201            }
202        };
203
204        client.publish_register_ops(op_batch).await?;
205
206        Ok(entry_hash)
207    }
208
209    pub(crate) fn get_register_address(&self, url: &SafeUrl) -> Result<RegisterAddress> {
210        let address = match url.address() {
211            DataAddress::Register(reg_address) => reg_address,
212            other => {
213                return Err(Error::ContentError(format!(
214                    "The url {url} has an {other:?} address. \
215                    To fetch register entries, this url must refer to a register.",
216                )))
217            }
218        };
219        Ok(address)
220    }
221}
222
223fn policy(owner: User) -> Policy {
224    let mut permissions = BTreeMap::new();
225    let _ = permissions.insert(owner, Permissions::new(true));
226    Policy { owner, permissions }
227}
228
229#[cfg(test)]
230mod tests {
231    use crate::{app::test_helpers::new_safe_instance, ContentType, Error};
232    use anyhow::{bail, Result};
233
234    #[tokio::test]
235    async fn test_register_create() -> Result<()> {
236        let safe = new_safe_instance().await?;
237
238        let xorurl = safe.register_create(None, 25_000, ContentType::Raw).await?;
239
240        let received_data = safe.register_read(&xorurl).await?;
241
242        assert!(received_data.is_empty());
243
244        let initial_data = "initial data bytes".as_bytes().to_vec();
245        let hash = safe
246            .register_write(&xorurl, initial_data.clone(), Default::default())
247            .await?;
248
249        let received_entry = safe.register_read_entry(&xorurl, hash).await?;
250
251        assert_eq!(received_entry, initial_data.clone());
252
253        Ok(())
254    }
255
256    #[tokio::test]
257    async fn test_register_owner_permissions() -> Result<()> {
258        let safe = new_safe_instance().await?;
259
260        let xorname = xor_name::rand::random();
261
262        let xorurl = safe
263            .register_create(Some(xorname), 25_000, ContentType::Raw)
264            .await?;
265
266        let received_data = safe.register_read(&xorurl).await?;
267
268        assert!(received_data.is_empty());
269
270        // now we check that trying to write to the same Registers with different owner shall fail
271        let safe = new_safe_instance().await?;
272
273        match safe
274            .register_write(&xorurl, b"dummy-pub-data".to_vec(), Default::default())
275            .await
276        {
277            Err(Error::AccessDenied(msg)) => {
278                assert_eq!(
279                    msg,
280                    format!("Couldn't write data on Register found at \"{xorurl}\"")
281                );
282            }
283            Err(err) => bail!("Error returned is not the expected: {:?}", err),
284            Ok(_) => bail!("Creation of Register succeeded unexpectedly".to_string()),
285        }
286
287        // now check that trying to read the same Registers with different owner
288        let _ = safe.register_read(&xorurl).await?;
289        Ok(())
290    }
291}