1use std::fs;
4use std::path::{Path, PathBuf};
5use std::time::{Duration, Instant};
6
7use nix::sys::signal::{Signal, kill};
8use nix::unistd::Pid;
9
10use crate::errors::{Result, SandboxError};
11use crate::execution::ProcessStream;
12use crate::execution::process::{ProcessConfig, ProcessExecutor};
13use crate::isolation::namespace::NamespaceConfig;
14use crate::isolation::seccomp::SeccompProfile;
15use crate::resources::cgroup::{Cgroup, CgroupConfig};
16use crate::utils;
17
18#[derive(Debug, Clone)]
20pub struct SandboxConfig {
21 pub root: PathBuf,
23 pub memory_limit: Option<u64>,
25 pub cpu_quota: Option<u64>,
27 pub cpu_period: Option<u64>,
29 pub max_pids: Option<u32>,
31 pub seccomp_profile: SeccompProfile,
33 pub namespace_config: NamespaceConfig,
35 pub timeout: Option<Duration>,
37 pub id: String,
39}
40
41impl Default for SandboxConfig {
42 fn default() -> Self {
43 Self {
44 root: PathBuf::from("/var/lib/sandbox"),
45 memory_limit: None,
46 cpu_quota: None,
47 cpu_period: None,
48 max_pids: None,
49 seccomp_profile: SeccompProfile::Minimal,
50 namespace_config: NamespaceConfig::default(),
51 timeout: None,
52 id: "default".to_string(),
53 }
54 }
55}
56
57impl SandboxConfig {
58 pub fn validate(&self) -> Result<()> {
60 utils::require_root()?;
61
62 self.validate_invariants()
63 }
64
65 fn validate_invariants(&self) -> Result<()> {
66 if self.id.is_empty() {
67 return Err(SandboxError::InvalidConfig(
68 "Sandbox ID cannot be empty".to_string(),
69 ));
70 }
71
72 if self.namespace_config.enabled_count() == 0 {
73 return Err(SandboxError::InvalidConfig(
74 "At least one namespace must be enabled".to_string(),
75 ));
76 }
77
78 Ok(())
79 }
80}
81
82pub struct SandboxBuilder {
84 config: SandboxConfig,
85}
86
87impl SandboxBuilder {
88 pub fn new(id: &str) -> Self {
90 Self {
91 config: SandboxConfig {
92 id: id.to_string(),
93 ..Default::default()
94 },
95 }
96 }
97
98 pub fn memory_limit(mut self, bytes: u64) -> Self {
100 self.config.memory_limit = Some(bytes);
101 self
102 }
103
104 pub fn memory_limit_str(self, s: &str) -> Result<Self> {
106 let bytes = utils::parse_memory_size(s)?;
107 Ok(self.memory_limit(bytes))
108 }
109
110 pub fn cpu_quota(mut self, quota: u64, period: u64) -> Self {
112 self.config.cpu_quota = Some(quota);
113 self.config.cpu_period = Some(period);
114 self
115 }
116
117 pub fn cpu_limit_percent(self, percent: u32) -> Self {
119 if percent == 0 || percent > 100 {
120 return self;
121 }
122 let quota = (percent as u64) * 1000; let period = 100000;
124 self.cpu_quota(quota, period)
125 }
126
127 pub fn max_pids(mut self, max: u32) -> Self {
129 self.config.max_pids = Some(max);
130 self
131 }
132
133 pub fn seccomp_profile(mut self, profile: SeccompProfile) -> Self {
135 self.config.seccomp_profile = profile;
136 self
137 }
138
139 pub fn root(mut self, path: impl AsRef<Path>) -> Self {
141 self.config.root = path.as_ref().to_path_buf();
142 self
143 }
144
145 pub fn timeout(mut self, duration: Duration) -> Self {
147 self.config.timeout = Some(duration);
148 self
149 }
150
151 pub fn namespaces(mut self, config: NamespaceConfig) -> Self {
153 self.config.namespace_config = config;
154 self
155 }
156
157 pub fn build(self) -> Result<Sandbox> {
159 self.config.validate()?;
160 Sandbox::new(self.config)
161 }
162}
163
164#[derive(Debug, Clone)]
166pub struct SandboxResult {
167 pub exit_code: i32,
169 pub signal: Option<i32>,
171 pub timed_out: bool,
173 pub memory_peak: u64,
175 pub cpu_time_us: u64,
177 pub wall_time_ms: u64,
179}
180
181impl SandboxResult {
182 pub fn killed_by_seccomp(&self) -> bool {
185 self.exit_code == 159
186 }
187
188 pub fn seccomp_error(&self) -> Option<&'static str> {
190 if self.killed_by_seccomp() {
191 Some("The action requires more permissions than were granted.")
192 } else {
193 None
194 }
195 }
196
197 pub fn check_seccomp_error(&self) -> crate::errors::Result<&SandboxResult> {
199 if self.killed_by_seccomp() {
200 Err(SandboxError::PermissionDenied(
201 "The seccomp profile is too restrictive for this operation. \
202 Try using a less restrictive profile (e.g., SeccompProfile::Compute or SeccompProfile::Unrestricted)"
203 .to_string(),
204 ))
205 } else {
206 Ok(self)
207 }
208 }
209}
210
211pub struct Sandbox {
213 config: SandboxConfig,
214 pid: Option<Pid>,
215 cgroup: Option<Cgroup>,
216 start_time: Option<Instant>,
217}
218
219impl Sandbox {
220 fn new(config: SandboxConfig) -> Result<Self> {
222 fs::create_dir_all(&config.root).map_err(|e| {
224 SandboxError::Io(std::io::Error::other(format!(
225 "Failed to create root directory: {}",
226 e
227 )))
228 })?;
229
230 Ok(Self {
231 config,
232 pid: None,
233 cgroup: None,
234 start_time: None,
235 })
236 }
237
238 pub fn id(&self) -> &str {
240 &self.config.id
241 }
242
243 pub fn root(&self) -> &Path {
245 &self.config.root
246 }
247
248 pub fn is_running(&self) -> bool {
250 self.pid.is_some()
251 }
252
253 pub fn run(&mut self, program: &str, args: &[&str]) -> Result<SandboxResult> {
255 if self.is_running() {
256 return Err(SandboxError::AlreadyRunning);
257 }
258
259 if !utils::is_root() {
260 return Err(SandboxError::PermissionDenied(
261 "Sandbox requires root privileges for proper isolation (cgroups, namespaces). \
262 Running without root would bypass all resource limits and namespace isolation. \
263 Use sudo or run as root."
264 .to_string(),
265 ));
266 }
267
268 self.start_time = Some(Instant::now());
269
270 let cgroup_name = format!("sandbox-{}", self.config.id);
271 let cgroup = Cgroup::new(&cgroup_name, Pid::from_raw(std::process::id() as i32))?;
272
273 let cgroup_config = CgroupConfig {
274 memory_limit: self.config.memory_limit,
275 cpu_quota: self.config.cpu_quota,
276 cpu_period: self.config.cpu_period,
277 max_pids: self.config.max_pids,
278 cpu_weight: None,
279 };
280 cgroup.apply_config(&cgroup_config)?;
281
282 self.cgroup = Some(cgroup);
283
284 let process_config = ProcessConfig {
285 program: program.to_string(),
286 args: args.iter().map(|s| s.to_string()).collect(),
287 env: Vec::new(),
288 cwd: None,
289 chroot_dir: None,
290 uid: None,
291 gid: None,
292 seccomp: Some(crate::isolation::seccomp::SeccompFilter::from_profile(
293 self.config.seccomp_profile.clone(),
294 )),
295 inherit_env: true,
296 };
297
298 let process_result =
299 ProcessExecutor::execute(process_config, self.config.namespace_config.clone())?;
300
301 self.pid = Some(process_result.pid);
302
303 let wall_time_ms = self.start_time.unwrap().elapsed().as_millis() as u64;
304 let (memory_peak, _) = self.get_resource_usage()?;
305
306 Ok(SandboxResult {
307 exit_code: process_result.exit_status,
308 signal: process_result.signal,
309 timed_out: false,
310 memory_peak,
311 cpu_time_us: process_result.exec_time_ms * 1000,
312 wall_time_ms,
313 })
314 }
315
316 pub fn run_with_stream(
318 &mut self,
319 program: &str,
320 args: &[&str],
321 ) -> Result<(SandboxResult, ProcessStream)> {
322 if self.is_running() {
323 return Err(SandboxError::AlreadyRunning);
324 }
325
326 self.start_time = Some(Instant::now());
327
328 let cgroup_name = format!("sandbox-{}", self.config.id);
329 let cgroup = Cgroup::new(&cgroup_name, Pid::from_raw(std::process::id() as i32))?;
330
331 let cgroup_config = CgroupConfig {
332 memory_limit: self.config.memory_limit,
333 cpu_quota: self.config.cpu_quota,
334 cpu_period: self.config.cpu_period,
335 max_pids: self.config.max_pids,
336 cpu_weight: None,
337 };
338 cgroup.apply_config(&cgroup_config)?;
339
340 self.cgroup = Some(cgroup);
341
342 let process_config = ProcessConfig {
343 program: program.to_string(),
344 args: args.iter().map(|s| s.to_string()).collect(),
345 env: Vec::new(),
346 cwd: None,
347 chroot_dir: None,
348 uid: None,
349 gid: None,
350 seccomp: Some(crate::isolation::seccomp::SeccompFilter::from_profile(
351 self.config.seccomp_profile.clone(),
352 )),
353 inherit_env: true,
354 };
355
356 let (process_result, stream) = ProcessExecutor::execute_with_stream(
357 process_config,
358 self.config.namespace_config.clone(),
359 true,
360 )?;
361
362 self.pid = Some(process_result.pid);
363
364 let wall_time_ms = self.start_time.unwrap().elapsed().as_millis() as u64;
365 let (memory_peak, _) = self.get_resource_usage().unwrap_or((0, 0));
366
367 let sandbox_result = SandboxResult {
368 exit_code: process_result.exit_status,
369 signal: process_result.signal,
370 timed_out: false,
371 memory_peak,
372 cpu_time_us: process_result.exec_time_ms * 1000,
373 wall_time_ms,
374 };
375
376 let stream =
377 stream.ok_or_else(|| SandboxError::Io(std::io::Error::other("stream unavailable")))?;
378
379 Ok((sandbox_result, stream))
380 }
381
382 pub fn kill(&mut self) -> Result<()> {
383 if let Some(pid) = self.pid {
384 kill(pid, Signal::SIGKILL)
385 .map_err(|e| SandboxError::Syscall(format!("Failed to kill process: {}", e)))?;
386 self.pid = None;
387 }
388 Ok(())
389 }
390
391 pub fn get_resource_usage(&self) -> Result<(u64, u64)> {
393 if let Some(ref cgroup) = self.cgroup {
394 let memory = cgroup.get_memory_usage()?;
395 let cpu = cgroup.get_cpu_usage()?;
396 Ok((memory, cpu))
397 } else {
398 Err(SandboxError::InvalidConfig(
399 "Cannot get resource usage: cgroup not initialized".to_string(),
400 ))
401 }
402 }
403}
404
405impl Drop for Sandbox {
406 fn drop(&mut self) {
407 let _ = self.kill();
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use crate::resources::cgroup::Cgroup;
415 use crate::test_support::serial_guard;
416 use crate::utils;
417 use std::env;
418 use std::time::Duration;
419 use tempfile::tempdir;
420
421 fn config_with_temp_root(id: &str) -> (tempfile::TempDir, SandboxConfig) {
422 let tmp = tempdir().unwrap();
423 let config = SandboxConfig {
424 id: id.to_string(),
425 root: tmp.path().join("root"),
426 namespace_config: NamespaceConfig::minimal(),
427 ..Default::default()
428 };
429 (tmp, config)
430 }
431
432 struct RootOverrideGuard;
433
434 impl RootOverrideGuard {
435 fn enable() -> Self {
436 utils::set_root_override(Some(true));
437 Self
438 }
439 }
440
441 impl Drop for RootOverrideGuard {
442 fn drop(&mut self) {
443 utils::set_root_override(None);
444 }
445 }
446
447 struct EnvVarGuard {
448 key: &'static str,
449 prev: Option<String>,
450 }
451
452 impl EnvVarGuard {
453 fn new(key: &'static str, value: &str) -> Self {
454 let prev = env::var(key).ok();
455 unsafe {
456 env::set_var(key, value);
457 }
458 Self { key, prev }
459 }
460 }
461
462 impl Drop for EnvVarGuard {
463 fn drop(&mut self) {
464 if let Some(ref value) = self.prev {
465 unsafe {
466 env::set_var(self.key, value);
467 }
468 } else {
469 unsafe {
470 env::remove_var(self.key);
471 }
472 }
473 }
474 }
475
476 #[test]
477 fn test_sandbox_config_default() {
478 let config = SandboxConfig::default();
479 assert_eq!(config.id, "default");
480 assert!(config.memory_limit.is_none());
481 }
482
483 #[test]
484 fn test_sandbox_config_validate() {
485 let config = SandboxConfig {
486 id: String::new(),
487 ..Default::default()
488 };
489
490 if let Err(e) = config.validate() {
493 assert!(e.to_string().contains("ID") || e.to_string().contains("root"));
495 }
496 }
497
498 #[test]
499 fn test_sandbox_builder_new() {
500 let builder = SandboxBuilder::new("test");
501 assert_eq!(builder.config.id, "test");
502 }
503
504 #[test]
505 fn test_sandbox_builder_memory_limit() {
506 let builder = SandboxBuilder::new("test").memory_limit(100 * 1024 * 1024);
507 assert_eq!(builder.config.memory_limit, Some(100 * 1024 * 1024));
508 }
509
510 #[test]
511 fn test_sandbox_builder_memory_limit_str() -> Result<()> {
512 let builder = SandboxBuilder::new("test").memory_limit_str("100M")?;
513 assert_eq!(builder.config.memory_limit, Some(100 * 1024 * 1024));
514 Ok(())
515 }
516
517 #[test]
518 fn test_sandbox_builder_cpu_limit() {
519 let builder = SandboxBuilder::new("test").cpu_limit_percent(50);
520 assert!(builder.config.cpu_quota.is_some());
521 }
522
523 #[test]
524 fn test_sandbox_builder_cpu_limit_zero() {
525 let builder = SandboxBuilder::new("test").cpu_limit_percent(0);
526 assert!(builder.config.cpu_quota.is_none());
527 }
528
529 #[test]
530 fn test_sandbox_builder_cpu_limit_over_100() {
531 let builder = SandboxBuilder::new("test").cpu_limit_percent(150);
532 assert!(builder.config.cpu_quota.is_none());
533 }
534
535 #[test]
536 fn test_sandbox_builder_cpu_quota() {
537 let builder = SandboxBuilder::new("test").cpu_quota(50000, 100000);
538 assert_eq!(builder.config.cpu_quota, Some(50000));
539 assert_eq!(builder.config.cpu_period, Some(100000));
540 }
541
542 #[test]
543 fn test_sandbox_builder_max_pids() {
544 let builder = SandboxBuilder::new("test").max_pids(10);
545 assert_eq!(builder.config.max_pids, Some(10));
546 }
547
548 #[test]
549 fn test_sandbox_builder_seccomp_profile() {
550 let builder = SandboxBuilder::new("test").seccomp_profile(SeccompProfile::IoHeavy);
551 assert_eq!(builder.config.seccomp_profile, SeccompProfile::IoHeavy);
552 }
553
554 #[test]
555 fn test_sandbox_builder_root() {
556 let tmp = tempdir().unwrap();
557 let builder = SandboxBuilder::new("test").root(tmp.path());
558 assert_eq!(builder.config.root, tmp.path());
559 }
560
561 #[test]
562 fn test_sandbox_builder_timeout() {
563 let builder = SandboxBuilder::new("test").timeout(Duration::from_secs(30));
564 assert_eq!(builder.config.timeout, Some(Duration::from_secs(30)));
565 }
566
567 #[test]
568 fn test_sandbox_builder_namespaces() {
569 let ns_config = NamespaceConfig::minimal();
570 let builder = SandboxBuilder::new("test").namespaces(ns_config.clone());
571 assert_eq!(builder.config.namespace_config, ns_config);
572 }
573
574 #[test]
575 fn test_sandbox_result() {
576 let result = SandboxResult {
577 exit_code: 0,
578 signal: None,
579 timed_out: false,
580 memory_peak: 1024,
581 cpu_time_us: 5000,
582 wall_time_ms: 100,
583 };
584 assert_eq!(result.exit_code, 0);
585 assert!(!result.timed_out);
586 }
587
588 #[test]
589 fn sandbox_config_invariants_detect_empty_id() {
590 let config = SandboxConfig {
591 id: String::new(),
592 ..Default::default()
593 };
594 assert!(config.validate_invariants().is_err());
595 }
596
597 #[test]
598 fn sandbox_config_invariants_detect_disabled_namespaces() {
599 let config = SandboxConfig {
600 namespace_config: NamespaceConfig {
601 pid: false,
602 ipc: false,
603 net: false,
604 mount: false,
605 uts: false,
606 user: false,
607 },
608 ..Default::default()
609 };
610 assert!(config.validate_invariants().is_err());
611 }
612
613 #[test]
614 fn sandbox_provides_id_and_root() {
615 let (_tmp, config) = config_with_temp_root("sand-id");
616 let sandbox = Sandbox::new(config.clone()).unwrap();
617 assert_eq!(sandbox.id(), "sand-id");
618 assert!(sandbox.root().ends_with("root"));
619 assert!(!sandbox.is_running());
620 }
621
622 #[test]
623 fn sandbox_run_requires_root() {
624 let _guard = serial_guard();
625 let (_tmp, config) = config_with_temp_root("run-test");
626 let mut sandbox = Sandbox::new(config).unwrap();
627 let args: [&str; 1] = ["hello"];
628 let result = sandbox.run("/bin/echo", &args);
629 assert!(result.is_err());
630 assert!(result.unwrap_err().to_string().contains("root"));
631 }
632
633 #[test]
634 fn sandbox_run_returns_error_if_already_running() {
635 let _guard = serial_guard();
636 let (_tmp, config) = config_with_temp_root("already-running");
637 let mut sandbox = Sandbox::new(config).unwrap();
638
639 sandbox.pid = Some(Pid::from_raw(1));
641
642 let args: [&str; 1] = ["test"];
643 let result = sandbox.run("/bin/echo", &args);
644
645 assert!(result.is_err());
646 assert!(result.unwrap_err().to_string().contains("already running"));
647 }
648
649 #[test]
650 fn test_sandbox_builder_build_creates_sandbox() {
651 let _guard = serial_guard();
652 let _root_guard = RootOverrideGuard::enable();
653 let tmp = tempdir().unwrap();
654 let sandbox = SandboxBuilder::new("build-test").root(tmp.path()).build();
655
656 assert!(sandbox.is_ok());
657 }
658
659 #[test]
660 fn test_sandbox_builder_build_validates_config() {
661 let _guard = serial_guard();
662 let tmp = tempdir().unwrap();
663 let result = SandboxBuilder::new("").root(tmp.path()).build();
664
665 assert!(result.is_err());
666 }
667
668 #[test]
669 fn sandbox_reports_resource_usage_from_cgroup() {
670 let (tmp, mut config) = config_with_temp_root("resource-test");
671 config.root = tmp.path().join("root");
672 let mut sandbox = Sandbox::new(config).unwrap();
673
674 let cg_path = tmp.path().join("cgroup");
675 std::fs::create_dir_all(&cg_path).unwrap();
676 std::fs::write(cg_path.join("memory.current"), "1234").unwrap();
677 std::fs::write(cg_path.join("cpu.stat"), "usage_usec 77\n").unwrap();
678
679 sandbox.cgroup = Some(Cgroup::for_testing(cg_path.clone()));
680 let (mem, cpu) = sandbox.get_resource_usage().unwrap();
681 assert_eq!(mem, 1234);
682 assert_eq!(cpu, 77);
683 }
684
685 #[test]
686 #[ignore]
687 fn sandbox_builder_builds_when_root_override() {
688 let _guard = serial_guard();
689 let _root_guard = RootOverrideGuard::enable();
690 let tmp = tempdir().unwrap();
691 let _env_guard = EnvVarGuard::new("SANDBOX_CGROUP_ROOT", tmp.path().to_str().unwrap());
692
693 let mut sandbox = SandboxBuilder::new("integration")
694 .memory_limit(1024)
695 .cpu_limit_percent(10)
696 .max_pids(4)
697 .seccomp_profile(SeccompProfile::Minimal)
698 .root(tmp.path())
699 .timeout(Duration::from_secs(1))
700 .namespaces(NamespaceConfig::minimal())
701 .build()
702 .unwrap();
703
704 let args: [&str; 0] = [];
705 let result = sandbox.run("/bin/true", &args).unwrap();
706 assert_eq!(result.exit_code, 0);
707 }
708
709 #[test]
710 fn sandbox_kill_handles_missing_pid() {
711 let (_tmp, config) = config_with_temp_root("kill-test");
712 let mut sandbox = Sandbox::new(config).unwrap();
713 sandbox.kill().unwrap();
714 }
715
716 #[test]
717 fn sandbox_kill_terminates_real_process() {
718 let (_tmp, config) = config_with_temp_root("kill-proc");
719 let mut sandbox = Sandbox::new(config).unwrap();
720 let mut child = std::process::Command::new("sleep")
721 .arg("1")
722 .spawn()
723 .unwrap();
724 sandbox.pid = Some(Pid::from_raw(child.id() as i32));
725 sandbox.kill().unwrap();
726 let _ = child.wait();
727 }
728
729 #[test]
730 fn sandbox_get_resource_usage_without_cgroup_fails() {
731 let (_tmp, config) = config_with_temp_root("no-cgroup");
732 let sandbox = Sandbox::new(config).unwrap();
733 let result = sandbox.get_resource_usage();
734 assert!(result.is_err());
735 }
736
737 #[test]
738 #[ignore]
739 fn sandbox_run_with_stream_captures_output() {
740 let _guard = serial_guard();
741 let _root_guard = RootOverrideGuard::enable();
742 let (_tmp, config) = config_with_temp_root("stream-test");
743 let mut sandbox = Sandbox::new(config).unwrap();
744
745 let (result, stream) = sandbox
746 .run_with_stream("/bin/echo", &["hello world"])
747 .unwrap();
748
749 let chunks: Vec<_> = stream.into_iter().collect();
750
751 assert!(!chunks.is_empty());
752 assert_eq!(result.exit_code, 0);
753
754 let has_stdout = chunks
755 .iter()
756 .any(|chunk| matches!(chunk, crate::StreamChunk::Stdout(_)));
757 let has_exit = chunks
758 .iter()
759 .any(|chunk| matches!(chunk, crate::StreamChunk::Exit { .. }));
760
761 assert!(has_stdout, "Should have captured stdout");
762 assert!(has_exit, "Should have exit chunk");
763 }
764
765 #[test]
766 fn test_sandbox_result_killed_by_seccomp() {
767 let result = SandboxResult {
768 exit_code: 159,
769 signal: None,
770 timed_out: false,
771 memory_peak: 0,
772 cpu_time_us: 0,
773 wall_time_ms: 0,
774 };
775 assert!(result.killed_by_seccomp());
776 }
777
778 #[test]
779 fn test_sandbox_result_not_killed_by_seccomp() {
780 let result = SandboxResult {
781 exit_code: 0,
782 signal: None,
783 timed_out: false,
784 memory_peak: 0,
785 cpu_time_us: 0,
786 wall_time_ms: 0,
787 };
788 assert!(!result.killed_by_seccomp());
789 }
790
791 #[test]
792 fn test_sandbox_result_seccomp_error_message() {
793 let result = SandboxResult {
794 exit_code: 159,
795 signal: None,
796 timed_out: false,
797 memory_peak: 0,
798 cpu_time_us: 0,
799 wall_time_ms: 0,
800 };
801 let msg = result.seccomp_error();
802 assert!(msg.is_some());
803 assert!(msg.unwrap().contains("permissions"));
804 }
805
806 #[test]
807 fn test_sandbox_result_check_seccomp_error_when_killed() {
808 let result = SandboxResult {
809 exit_code: 159,
810 signal: None,
811 timed_out: false,
812 memory_peak: 0,
813 cpu_time_us: 0,
814 wall_time_ms: 0,
815 };
816 let check_result = result.check_seccomp_error();
817 assert!(check_result.is_err());
818 let err = check_result.unwrap_err();
819 assert!(err.to_string().contains("restrictive"));
820 }
821
822 #[test]
823 fn test_sandbox_result_check_seccomp_error_when_success() {
824 let result = SandboxResult {
825 exit_code: 0,
826 signal: None,
827 timed_out: false,
828 memory_peak: 0,
829 cpu_time_us: 0,
830 wall_time_ms: 0,
831 };
832 let check_result = result.check_seccomp_error();
833 assert!(check_result.is_ok());
834 }
835}