1use crate::storage::db::{
6 delete_user_auth, delete_user_display_name, delete_user_id,
7 delete_user_kek_params, delete_user_picture, delete_user_pubkey,
8 delete_user_uuid, delete_user_wrapped_dek, get_user_auth, get_user_id,
9 get_user_kek_params, get_user_name, get_user_pictures, get_user_pubkey,
10 get_user_wrapped_dek, put_user_auth, put_user_id, put_user_kek_params,
11 put_user_uuid, put_user_wrapped_dek,
12};
13use crate::storage::graph::Graph;
14use crate::storage::pc_settings::ensure_pc_settings;
15use crate::storage::user::auth::{KekParams, Secret};
16use crate::utilities::password::{Password, hash, verify};
17use crate::utilities::resource_lock::{Lock, ResourceLock};
18use crate::{bail_if_none, error, get_storage_dir, json, log};
19use anyhow::{Context, Result, anyhow, bail};
20use serde::{Deserialize, Serialize, de::DeserializeOwned};
21use std::collections::HashMap;
22use std::ffi::OsString;
23use std::fs;
24use std::path::Path;
25
26pub mod auth;
27
28pub const TEST_USER_PASS: &str =
29 "test_password_523ed9db-1885-439d-83d5-61c869e5223d";
30pub const TEST_USER_PHC: &str = "$argon2id$v=19$m=19456,t=2,p=1$RXiQwP6rcmLh98qumvyu0g$Pc9dem17Dgwz6uoiCuceMh+VYQxZ8WqSK36gfpAZYXY";
31
32#[derive(Debug)]
33pub struct NameAndIdLock {
34 pub name_lock: ResourceLock,
35 pub id_lock: ResourceLock,
36}
37
38impl Lock for NameAndIdLock {}
39
40impl NameAndIdLock {
41 pub fn new(name_lock: ResourceLock, id_lock: ResourceLock) -> Self {
42 Self { name_lock, id_lock }
43 }
44}
45
46#[derive(Debug, Default, Serialize, Deserialize)]
47pub struct UserLocalConfig {
48 pub default_store_location: OsString,
49}
50
51#[derive(Debug, Default, Serialize)]
52pub struct UserPublicInfo {
53 local_id: u64,
54 name: String,
55 display_name: Option<Vec<u8>>,
56 uuid: Vec<u8>,
57 picture: Option<Vec<u8>>,
58 #[serde(skip)]
59 lock: Option<ResourceLock>,
60}
61
62impl UserPublicInfo {
63 pub fn get_by_name(name: &str) -> Result<Option<Self>> {
64 let _name_lock = ResourceLock::acquire(USER_NAME_LOCK, &name)?;
65 let mut user_info = UserPublicInfo::default();
66 let id = get_user_id_by_name(name);
67 let Some(id) = id else {
68 return Ok(None);
69 };
70 let user_info_lock = bail_if_none!(
71 ResourceLock::acquire(USER_PUBLIC_INFO_LOCK, &id).ok()
72 );
73 let id_check = bail_if_none!(get_user_id_by_name(name));
75 if id != id_check {
76 bail!(format!(
77 "User ID changed during get_by_name for '{name}': {id} -> {id_check}"
78 ));
79 }
80 user_info.lock = Some(user_info_lock);
81 user_info.local_id = id;
82 user_info.name = name.to_string();
84 if let Some(picture) = get_user_picture_by_id(id) {
85 user_info.picture = Some(picture);
86 }
87 Ok(Some(user_info))
88 }
89
90 pub fn get_by_id(id: u64) -> Option<Self> {
91 let mut user_info = UserPublicInfo::default();
92 let user_info_lock =
93 ResourceLock::acquire(USER_PUBLIC_INFO_LOCK, &id).ok()?;
94 user_info.lock = Some(user_info_lock);
95 user_info.local_id = id;
96
97 user_info.name = get_user_name(id)?;
99
100 if let Some(picture) = { get_user_picture_by_id(id) } {
101 user_info.picture = Some(picture);
102 }
103 Some(user_info)
104 }
105
106 pub fn local_id(&self) -> u64 {
107 self.local_id
108 }
109 pub fn name(&self) -> &str {
110 &self.name
111 }
112 pub fn display_name(&self) -> Option<&[u8]> {
113 self.display_name.as_deref()
114 }
115 pub fn uuid(&self) -> &Vec<u8> {
116 &self.uuid
117 }
118 pub fn user_picture(&self) -> Option<&[u8]> {
119 self.picture.as_deref()
120 }
121
122 pub fn list_all() -> Result<Vec<Self>> {
123 let user_ids = get_all_user_ids()?;
124 let mut users = Vec::new();
125 for id in user_ids {
126 if let Some(user) = UserPublicInfo::get_by_id(id) {
127 users.push(user);
128 }
129 }
130 Ok(users)
131 }
132}
133
134#[derive(Debug, Default)]
135pub struct User {
136 public_info: UserPublicInfo,
137 remote_id: Option<u64>,
138 local_config: UserLocalConfig,
139 graphs: Vec<Graph>,
140 dek: Option<Secret>,
141 lock: Option<NameAndIdLock>, }
143
144const USER_LOCK: &str = "user";
145const USER_NAME_LOCK: &str = "user_name";
146const USER_PUBLIC_INFO_LOCK: &str = "user_public_info";
147
148impl User {
149 pub fn name(&self) -> String {
150 self.public_info.name.clone()
151 }
152
153 pub fn local_id(&self) -> u64 {
154 self.public_info.local_id
155 }
156
157 pub fn remote_id(&self) -> Option<u64> {
158 self.remote_id
159 }
160
161 pub fn display_name(&self) -> Option<&[u8]> {
162 self.public_info.display_name.as_deref()
163 }
164
165 pub fn user_picture(&self) -> Option<&[u8]> {
166 self.public_info.picture.as_deref()
167 }
168
169 pub fn uuid(&self) -> &Vec<u8> {
170 &self.public_info.uuid
171 }
172
173 pub fn create(name: &str, password: &Password) -> Result<Self> {
175 let name_lock = ResourceLock::acquire(USER_NAME_LOCK, &name)?;
177
178 error!(format!("Creating user '{name}'"));
179 ensure_base_layout().context("Failed to ensure base layout")?;
180
181 if get_user_id_by_name(name).is_some() {
183 return Err(anyhow!("User '{name}' already exists"));
184 }
185
186 let user_id = next_user_id().context("Unable to allocate user id")?;
188
189 let id_lock = lock_by_id(user_id)?;
191 let user_lock = NameAndIdLock { name_lock, id_lock };
192
193 let (kek, kek_params) = auth::derive_kek(password);
194 let dek = auth::generate_dek();
195 let phc = hash(password)?;
196
197 let uuid = uuid::Uuid::new_v4();
207 let uuid = uuid.as_bytes();
208
209 let aad = uuid;
210 let wrapped_dek = auth::wrap_key(&kek, &dek, aad);
211
212 let root = get_storage_dir().context("No storage dir")?;
214 let users_dir = root.join("users");
215 fs::create_dir_all(&users_dir).ok();
216
217 put_user_id(name, user_id)?;
218 put_user_uuid(user_id, uuid)?;
219 put_user_auth(user_id, &phc)?;
220 put_user_kek_params(user_id, json!(kek_params).as_bytes())?;
221 put_user_wrapped_dek(user_id, &wrapped_dek)?;
222
223 ensure_pc_settings().context("Failed to ensure pc_settings.json")?;
224
225 {
226 let root = get_storage_dir().context("No storage dir")?;
227 let graphs_dir = root.join("graphs").join(user_id.to_string());
228 fs::create_dir_all(&graphs_dir).with_context(|| {
229 format!("Failed to create graphs dir {}", graphs_dir.display())
230 })?;
231 }
232
233 let mut user = User::default();
234 user.public_info.local_id = user_id;
235 user.public_info.name = name.to_string();
236 user.public_info.uuid = uuid.to_vec();
237 user.local_config = user.local_config();
238 user.dek = Some(Secret::new(dek));
239 user.lock = Some(user_lock);
240
241 Ok(user)
242 }
243
244 pub fn delete(&self) -> Result<()> {
246 ensure_base_layout().context("Failed to ensure base layout")?;
247 let user_id = self.local_id();
248 let name = self.name();
249
250 if self.lock.is_some() {
252 Self::_delete_user(user_id, &name)
253 } else {
254 let _name_lock = ResourceLock::acquire(USER_NAME_LOCK, &name)?;
256 let _id_lock = lock_by_id(user_id)?;
257
258 Self::_delete_user(user_id, &name)
259 }
260 }
261
262 pub fn delete_by_name(name: &str) -> Result<()> {
264 ensure_base_layout().context("Failed to ensure base layout")?;
265 log!("Deleting user '{name}'");
266
267 let name_lock = ResourceLock::acquire(USER_NAME_LOCK, &name)?;
269
270 let user_id = get_user_id_by_name(name)
271 .ok_or_else(|| anyhow!("User ID not found for name {name}"))?;
272
273 let _id_lock = lock_by_id(user_id)?;
274
275 let res = {
277 let _keep_name_lock = &name_lock;
279 Self::_delete_user(user_id, name)
280 };
281
282 {
284 if get_user_id_by_name(name).is_some() {
285 bail!("Failed to delete user {name} with id {user_id}");
286 }
287 }
288 res
289 }
290
291 fn _delete_user(user_id: u64, name: &str) -> Result<()> {
296 let root = get_storage_dir().context("No storage dir")?;
297
298 delete_user_uuid(user_id)?;
299 delete_user_auth(user_id)?;
300 delete_user_picture(user_id)?;
301 delete_user_display_name(user_id)?;
302 delete_user_kek_params(user_id)?;
303 delete_user_wrapped_dek(user_id)?;
304 delete_user_pubkey(user_id)?;
305 delete_user_id(name, user_id)?;
306
307 let graphs_dir = root.join("graphs").join(user_id.to_string());
309 if graphs_dir.exists() {
310 fs::remove_dir_all(&graphs_dir).with_context(|| {
311 format!("Failed to remove graphs dir {graphs_dir:?}")
312 })?;
313 }
314 Ok(())
315 }
316
317 pub fn login(
320 public_info: UserPublicInfo,
321 password: &Password,
322 ) -> Result<Self> {
323 ensure_base_layout().context("Failed to ensure base layout")?;
324
325 if public_info.local_id == 0 {
326 return Err(anyhow!(
327 "UserPublicInfo.local_id was 0. Provide a valid user_id or use UserPublicInfo::get_by_name."
328 ));
329 }
330
331 let user_id = public_info.local_id;
332
333 let _user_lock = lock_by_id(user_id)?;
336
337 let (phc, kek_params, wrapped_dek, uuid) = {
338 let phc = get_user_password_by_id(user_id).ok_or_else(|| {
339 anyhow!("Auth entry for user_id {user_id} not found")
340 })?;
341
342 let kek_params = get_user_kek_params_by_id(user_id)
343 .and_then(|bytes| {
344 serde_json::from_slice::<KekParams>(&bytes).ok()
345 })
346 .ok_or_else(|| {
347 anyhow!("KEK params not found for user_id {user_id}")
348 })?;
349
350 let wrapped_dek =
351 get_user_wrapped_dek_by_id(user_id).ok_or_else(|| {
352 anyhow!("Wrapped DEK not found for user_id {user_id}")
353 })?;
354
355 if public_info.uuid.is_empty() {
357 }
359
360 (phc, kek_params, wrapped_dek, public_info.uuid.clone())
361 };
362
363 if !(verify(password, &phc)?) {
364 return Err(anyhow!("Invalid password"));
365 }
366
367 let (kek, _ignored_params) = auth::derive_kek(password);
370
371 let dek = auth::unwrap_key(&kek, &wrapped_dek, &uuid)
372 .context("Failed to unwrap DEK")?;
373
374 let mut user = User::default();
375 user.public_info = public_info;
376 user.local_config = user.local_config();
377 user.dek = Some(Secret::new(dek));
378 Ok(user)
380 }
381
382 pub fn local_config(&self) -> UserLocalConfig {
383 let cache_dir = get_storage_dir().unwrap();
384 let user_cache_dir = std::path::Path::new(&cache_dir).join(self.name());
385 std::fs::create_dir_all(&user_cache_dir)
386 .expect("Failed to create per-user cache directory");
387 let config_path = user_cache_dir.join("local_config");
388 if !config_path.exists() {
389 let default_config = UserLocalConfig {
390 default_store_location: cache_dir.into_os_string(),
391 };
392 let json = serde_json::to_string_pretty(&default_config).unwrap();
393 std::fs::write(&config_path, json)
394 .expect("Failed to write default local_config");
395 return default_config;
396 }
397 let json = std::fs::read_to_string(&config_path)
398 .expect("Failed to read local_config");
399 serde_json::from_str(&json).expect("Failed to deserialize local_config")
400 }
401
402 pub fn create_graph(&mut self, label: &str) -> Result<&Graph> {
403 let new_graph_id = if self.graphs.is_empty() {
404 1
405 } else {
406 self.graphs
407 .iter()
408 .map(|g| g.graph_id)
409 .max()
410 .ok_or_else(|| anyhow!("Failed to get max graph id"))?
411 + 1
412 };
413 let new_graph = Graph::new(new_graph_id, label, self);
414 self.graphs.push(new_graph);
415 self.graphs
416 .last()
417 .ok_or_else(|| anyhow!("Failed to get max graph id"))
418 }
419
420 pub fn get_graph_count(&self) -> usize {
421 self.graphs.len()
422 }
423
424 pub fn get_graph_by_id(&self, id: u32) -> Option<&Graph> {
425 self.graphs.iter().find(|g| g.graph_id == id)
426 }
427}
428
429pub fn lock_by_name(
430 name: &str,
431) -> Result<(ResourceLock, Option<ResourceLock>)> {
432 let name_lock = ResourceLock::acquire(USER_NAME_LOCK, &name)?;
434 let maybe_user = UserPublicInfo::get_by_name(name)?;
436 let mut id_lock = None;
437 if let Some(user) = maybe_user {
438 let id = user.local_id;
439 id_lock = Some(lock_by_id(id)?);
440 let user_check = UserPublicInfo::get_by_name(name)?;
442 if let Some(user_check) = user_check {
443 if id != user_check.local_id {
444 log!(format!(
445 "User ID changed during get_by_name for '{}': {} -> {}",
446 name, id, user_check.local_id
447 ));
448 bail!("User ID changed during lock acquisition");
449 }
450 }
451 }
452 Ok((name_lock, id_lock))
453}
454
455pub fn lock_by_id(user_id: u64) -> Result<ResourceLock> {
456 let id_lock = ResourceLock::acquire(USER_LOCK, &user_id.to_string())?;
457 Ok(id_lock)
458}
459
460fn get_user_id_by_name(name: &str) -> Option<u64> {
461 get_user_id(name)
462}
463
464fn get_user_password_by_id(user_id: u64) -> Option<String> {
465 get_user_auth(user_id)
466}
467
468fn get_user_picture_by_id(user_id: u64) -> Option<Vec<u8>> {
469 get_user_pictures(user_id)
470}
471
472fn get_user_kek_params_by_id(user_id: u64) -> Option<Vec<u8>> {
473 get_user_kek_params(user_id)
474}
475
476fn get_user_wrapped_dek_by_id(user_id: u64) -> Option<Vec<u8>> {
477 get_user_wrapped_dek(user_id)
478}
479
480fn get_user_pubkey_by_id(user_id: u64) -> Option<Vec<u8>> {
481 get_user_pubkey(user_id)
482}
483
484pub fn get_all_user_ids() -> Result<Vec<u64>> {
485 ensure_base_layout().context("Failed to ensure base layout")?;
486
487 crate::storage::db::get_all_user_ids()
488}
489
490fn ensure_base_layout() -> Result<()> {
493 let root = get_storage_dir().context("No storage dir")?;
494 let config_dir = root.join("config");
495 let users_dir = root.join("users");
496 let graphs_dir = root.join("graphs");
497 fs::create_dir_all(&config_dir)?;
498 fs::create_dir_all(&users_dir)?;
499 fs::create_dir_all(&graphs_dir)?;
500 Ok(())
501}
502
503fn next_user_id() -> Result<u64> {
506 use fs2::FileExt;
507 use std::fs::OpenOptions;
508 use std::io::{Read, Seek, Write};
509
510 let root = get_storage_dir().context("No storage dir")?;
511 let last_id_path = root.join("users").join("last_user_id");
512
513 let mut file = OpenOptions::new()
515 .read(true)
516 .write(true)
517 .truncate(false)
518 .create(true)
519 .open(&last_id_path)
520 .with_context(|| {
521 format!("Failed to open {}", last_id_path.display())
522 })?;
523
524 file.lock_exclusive().with_context(|| {
526 format!("Failed to lock {}", last_id_path.display())
527 })?;
528
529 file.seek(std::io::SeekFrom::Start(0))?;
531 let mut content = String::new();
532 file.read_to_string(&mut content)?;
533 let last_id = if content.trim().is_empty() {
534 0
535 } else {
536 content.trim().parse::<u64>()?
537 };
538
539 let next_id = last_id
541 .checked_add(1)
542 .ok_or_else(|| anyhow!("User ID overflow"))?;
543
544 file.seek(std::io::SeekFrom::Start(0))?;
546 file.set_len(0)?; file.write_all(next_id.to_string().as_bytes())
548 .with_context(|| {
549 format!("Failed to write {}", last_id_path.display())
550 })?;
551
552 Ok(next_id)
554}
555
556fn read_json_map<T: DeserializeOwned>(
557 path: &Path,
558) -> Result<HashMap<String, T>> {
559 if !path.exists() {
560 return Ok(HashMap::new());
561 }
562 let data = fs::read(path)?;
563 if data.is_empty() {
564 return Ok(HashMap::new());
565 }
566 let map =
567 serde_json::from_slice::<HashMap<String, T>>(&data).unwrap_or_default();
568 Ok(map)
569}
570
571fn write_json_map<T: Serialize>(
572 path: &Path,
573 map: &HashMap<String, T>,
574) -> Result<()> {
575 let tmp = serde_json::to_vec_pretty(map)?;
576 let tmp_path = path.with_extension("tmp");
577 fs::write(&tmp_path, tmp)?;
578 fs::rename(tmp_path, path)?;
579 Ok(())
580}
581
582#[cfg(test)]
583pub fn get_test_user(name: &str) -> User {
584 use crate::debug;
585
586 let _ = lock_by_name(name).expect("Could not lock name");
587 debug!(
588 "Thread {} acquired lock for test user '{}'",
589 std::thread::current().name().unwrap_or("unnamed"),
590 name
591 );
592 User::delete_by_name(name).ok();
593
594 assert!(
595 !get_user_id_by_name(name).is_some(),
596 "Failed to delete test user."
597 );
598 debug!(
599 "Thread {} sees that test user not exists '{}'",
600 std::thread::current().name().unwrap_or("unnamed"),
601 name
602 );
603
604 let password = get_test_password();
605 let user = User::create(name, &password).expect(
606 format!(
607 "User creation failed {}",
608 std::thread::current().name().unwrap_or("unnamed")
609 )
610 .as_str(),
611 );
612 debug!(
613 "Thread {} was able to create user '{}'",
614 std::thread::current().name().unwrap_or("unnamed"),
615 name
616 );
617
618 user
619}
620
621#[cfg(test)]
622fn get_test_password() -> Password {
623 Password::from_string(TEST_USER_PASS)
624}
625
626#[cfg(test)]
627#[allow(clippy::unwrap_in_result, clippy::panic_in_result_fn)]
628mod tests {
629 use super::*;
630 use crate::{bail_if_none, debug};
631
632 fn get_test_user(name: &str) -> User {
633 super::get_test_user(name)
634 }
635
636 #[crate::ctb_test]
637 fn test_user_name_and_local_id() {
638 let user = User {
639 public_info: UserPublicInfo {
640 local_id: 42,
641 name: "alice".to_string(),
642 ..Default::default()
643 },
644 ..Default::default()
645 };
646 assert_eq!(user.name(), "alice");
647 assert_eq!(user.local_id(), 42);
648 }
649
650 #[crate::ctb_test]
651 fn test_user_picture_none() {
652 let user = User {
653 public_info: UserPublicInfo {
654 picture: None,
655 ..Default::default()
656 },
657 ..Default::default()
658 };
659 assert!(user.user_picture().is_none());
660 }
661
662 #[crate::ctb_test]
663 fn test_user_picture_some() {
664 let pic = vec![1, 2, 3];
665 let user = User {
666 public_info: UserPublicInfo {
667 picture: Some(pic.clone()),
668 ..Default::default()
669 },
670 ..Default::default()
671 };
672 assert_eq!(user.user_picture(), Some(pic.as_slice()));
673 }
674
675 #[crate::ctb_test]
676 fn test_create_and_get_graph_by_id() -> Result<()> {
677 let mut user = User {
678 public_info: UserPublicInfo {
679 local_id: 1,
680 name: "test_graph".to_string(),
681 ..Default::default()
682 },
683 ..Default::default()
684 };
685 let label = "test_graph_label";
686 let graph = user.create_graph(label)?;
687 assert_eq!(graph.label, label);
688 let id = graph.graph_id;
689 let fetched = bail_if_none!(user.get_graph_by_id(id));
690 assert_eq!(fetched.label, label);
691 Ok(())
692 }
693
694 #[crate::ctb_test]
695 fn test_get_graph_by_id_none() {
696 let user = User::default();
697 assert!(user.get_graph_by_id(12345).is_none());
698 }
699
700 #[crate::ctb_test]
701 fn test_get_all_user_ids() -> Result<()> {
702 let user = get_test_user(format!("{}_1", function_name!()).as_str());
703 let user2 = get_test_user(format!("{}_2", function_name!()).as_str());
704 let ids = get_all_user_ids()?;
705 assert!(ids.contains(&user.local_id()));
706 assert!(ids.contains(&user2.local_id()));
707 user.delete()?;
708 user2.delete()?;
709 Ok(())
710 }
711
712 #[crate::ctb_test]
713 fn test_create_and_login_user() -> Result<()> {
714 let name = function_name!();
715 User::delete_by_name(name).ok(); let password = Password::from_string(TEST_USER_PASS);
718 let user = super::get_test_user(name);
719 assert_eq!(user.name(), name);
720
721 let public_info = bail_if_none!(UserPublicInfo::get_by_name(name)?);
722 let logged_in = User::login(public_info, &password)?;
723 assert_eq!(logged_in.name(), name);
724
725 logged_in.delete()?;
727 Ok(())
728 }
729
730 #[crate::ctb_test]
731 fn test_create_and_delete_user() -> Result<()> {
732 let user = get_test_user(function_name!());
733 user.delete()?;
734 assert!(get_user_id_by_name(&user.name()).is_none());
735 debug!(get_user_password_by_id(user.local_id()));
736 assert!(get_user_password_by_id(user.local_id()).is_none());
737 assert!(get_user_picture_by_id(user.local_id()).is_none());
738 assert!(get_user_kek_params_by_id(user.local_id()).is_none());
739 assert!(get_user_wrapped_dek_by_id(user.local_id()).is_none());
740 assert!(get_user_pubkey_by_id(user.local_id()).is_none());
741 assert!(!fs::exists(
742 get_storage_dir()?
743 .join("graphs")
744 .join(user.local_id().to_string())
745 )?);
746 Ok(())
747 }
748
749 #[crate::ctb_test]
750 fn test_create_duplicate_user_fails() -> Result<()> {
751 let name = function_name!();
752 let _ = lock_by_name(name).expect("Could not lock name");
753 let user = get_test_user(name);
754 drop(user);
755 let res = User::create(name, &get_test_password());
756 assert!(res.is_err());
757 get_test_user(name).delete()?;
758 Ok(())
759 }
760
761 #[crate::ctb_test]
762 fn test_login_invalid_password_fails() -> Result<()> {
763 let user = get_test_user(function_name!());
764
765 let picture = user.user_picture();
768 let new_picture: Option<Vec<u8>> = if picture.is_none() {
769 None
770 } else {
771 Some(
772 user.user_picture()
773 .ok_or(anyhow::anyhow!("Failed to get user picture"))?
774 .to_vec(),
775 )
776 };
777
778 let new_public_info = UserPublicInfo {
779 local_id: user.local_id(),
780 name: user.name().clone(),
781 display_name: user.display_name().clone().map(|v| v.to_vec()),
782 uuid: user.uuid().clone(),
783 picture: new_picture,
784 lock: None,
785 };
786
787 drop(user);
789
790 let res = User::login(
791 new_public_info,
792 &Password::from_string("wrong_password"),
793 );
794 assert!(res.is_err());
795
796 Ok(())
797 }
798
799 #[crate::ctb_test]
800 fn test_local_config_roundtrip() -> Result<()> {
801 let user = get_test_user(function_name!());
802
803 let config = user.local_config();
804 assert_eq!(
805 config.default_store_location,
806 get_storage_dir()?.into_os_string()
807 );
808 user.delete()?;
809 Ok(())
810 }
811}