Skip to main content

mimobox_sdk/
config.rs

1use std::path::PathBuf;
2use std::time::Duration;
3
4#[cfg(feature = "vm")]
5use crate::error::SdkError;
6use mimobox_core::{SandboxConfig, SeccompProfile};
7
8/// Isolation level selection strategy.
9///
10/// Controls which sandboxing backend is used. `Auto` enables smart routing
11/// based on command type and trust level; explicit variants force a specific backend.
12///
13/// # Smart Routing Rules (Auto)
14///
15/// - `.wasm` / `.wat` / `.wast` files → `Wasm`
16/// - `TrustLevel::Untrusted` on Linux + `vm` feature → `MicroVm`
17/// - All other commands → `Os`
18///
19/// # Examples
20///
21/// ```
22/// use mimobox_sdk::IsolationLevel;
23///
24/// let level = IsolationLevel::Auto;
25/// assert_eq!(level, IsolationLevel::default());
26/// ```
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum IsolationLevel {
29    /// Smart routing based on command type and `TrustLevel`.
30    #[default]
31    Auto,
32    /// OS-level isolation: Landlock + Seccomp + Namespaces (Linux) or Seatbelt (macOS).
33    Os,
34    /// Wasm-level isolation via Wasmtime. Sub-millisecond cold start.
35    Wasm,
36    /// microVM-level isolation via KVM. Hardware-enforced boundary.
37    MicroVm,
38}
39
40/// Trust level for code being executed.
41///
42/// Affects auto-routing decisions when `IsolationLevel::Auto` is used.
43/// `Untrusted` code is always routed to the strongest available isolation.
44///
45/// # Fail-Closed Behavior
46///
47/// When `TrustLevel::Untrusted` is set and the microVM backend is unavailable
48/// (non-Linux platform or `vm` feature not enabled), the SDK returns an error
49/// instead of silently downgrading to OS-level isolation.
50///
51/// # Examples
52///
53/// ```
54/// use mimobox_sdk::TrustLevel;
55///
56/// let level = TrustLevel::SemiTrusted;
57/// assert_eq!(level, TrustLevel::default());
58/// ```
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub enum TrustLevel {
61    /// Trusted code: self-authored or fully audited.
62    Trusted,
63    /// Semi-trusted code: third-party libraries or partially audited code.
64    #[default]
65    SemiTrusted,
66    /// Untrusted code: user-submitted, downloaded, or otherwise unverified.
67    Untrusted,
68}
69
70/// Network access policy for the sandbox.
71///
72/// Controls whether the sandbox can access the network and through which channels.
73/// The default policy denies all network access.
74///
75/// # Examples
76///
77/// ```
78/// use mimobox_sdk::NetworkPolicy;
79///
80/// let policy = NetworkPolicy::DenyAll;
81/// match policy {
82///     NetworkPolicy::DenyAll => {},
83///     _ => panic!("expected DenyAll"),
84/// }
85/// ```
86#[derive(Debug, Clone, Default)]
87pub enum NetworkPolicy {
88    /// Deny all network access. Default policy.
89    #[default]
90    DenyAll,
91    /// Keep direct sandbox network blocked, but allow HTTP requests through the
92    /// host-side controlled proxy to the specified domains.
93    AllowDomains(Vec<String>),
94    /// Allow unrestricted network access. Uses a permissive Seccomp profile.
95    AllowAll,
96}
97
98/// SDK-level configuration for sandbox creation and behavior.
99///
100/// Use [`Config::builder()`] to construct a `Config` with the builder pattern.
101/// All fields have sensible defaults that prioritize security.
102///
103/// # Key Behaviors
104///
105/// - **Memory**: For the microVM backend, effective guest memory is
106///   `min(memory_limit_mb, vm_memory_mb)`.
107/// - **Timeout**: Internally rounded up to whole seconds. For example,
108///   `1500ms` becomes `2s`.
109/// - **HTTP domains**: The `allowed_http_domains` list supports glob patterns
110///   like `*.openai.com`. It is combined with `NetworkPolicy::AllowDomains`.
111///
112/// # Examples
113///
114/// ```
115/// use mimobox_sdk::Config;
116///
117/// let config = Config::default();
118/// assert_eq!(config.vm_vcpu_count, 1);
119/// assert_eq!(config.vm_memory_mb, 256);
120/// ```
121#[derive(Debug, Clone)]
122pub struct Config {
123    /// Isolation level selection strategy. `Auto` enables smart routing.
124    pub isolation: IsolationLevel,
125    /// Trust level affecting auto-routing decisions.
126    pub trust_level: TrustLevel,
127    /// Network access policy.
128    pub network: NetworkPolicy,
129    /// Command execution timeout. `None` means no timeout.
130    pub timeout: Option<Duration>,
131    /// Memory limit in MiB. Applied via cgroups v2 or setrlimit.
132    pub memory_limit_mb: Option<u64>,
133    /// CPU time quota in microseconds. `None` means unlimited.
134    pub cpu_quota_us: Option<u64>,
135    /// CPU period in microseconds. Defaults to 100000 (100ms).
136    pub cpu_period_us: u64,
137    /// Read-only mount paths inside the sandbox.
138    pub fs_readonly: Vec<PathBuf>,
139    /// Read-write mount paths inside the sandbox.
140    pub fs_readwrite: Vec<PathBuf>,
141    /// HTTP proxy domain whitelist (supports glob patterns like `*.openai.com`).
142    pub allowed_http_domains: Vec<String>,
143    /// Whether to allow child process creation (fork/clone) inside the sandbox.
144    pub allow_fork: bool,
145    /// microVM vCPU count. Only affects the microVM backend.
146    pub vm_vcpu_count: u8,
147    /// microVM guest memory size in MiB. Capped by `memory_limit_mb` if set.
148    pub vm_memory_mb: u32,
149    /// Custom microVM kernel image path. Falls back to `~/.mimobox/assets/vmlinux` if unset.
150    pub kernel_path: Option<PathBuf>,
151    /// Custom microVM rootfs path. Falls back to `~/.mimobox/assets/rootfs.cpio.gz` if unset.
152    pub rootfs_path: Option<PathBuf>,
153}
154
155impl Default for Config {
156    fn default() -> Self {
157        Self {
158            isolation: IsolationLevel::Auto,
159            trust_level: TrustLevel::default(),
160            network: NetworkPolicy::default(),
161            timeout: Some(Duration::from_secs(30)),
162            memory_limit_mb: Some(512),
163            cpu_quota_us: None,
164            cpu_period_us: 100_000,
165            fs_readonly: vec![
166                "/usr".into(),
167                "/lib".into(),
168                "/lib64".into(),
169                "/bin".into(),
170                "/sbin".into(),
171                "/dev".into(),
172                "/proc".into(),
173                "/etc".into(),
174            ],
175            fs_readwrite: vec!["/tmp".into()],
176            allowed_http_domains: Vec::new(),
177            allow_fork: false,
178            vm_vcpu_count: 1,
179            vm_memory_mb: 256,
180            kernel_path: None,
181            rootfs_path: None,
182        }
183    }
184}
185
186impl Config {
187    /// Returns a [`ConfigBuilder`] for constructing a `Config` with the builder pattern.
188    ///
189    /// # Examples
190    ///
191    /// ```
192    /// use mimobox_sdk::{Config, IsolationLevel};
193    /// use std::time::Duration;
194    ///
195    /// let config = Config::builder()
196    ///     .isolation(IsolationLevel::Os)
197    ///     .timeout(Duration::from_secs(10))
198    ///     .build();
199    ///
200    /// assert_eq!(config.isolation, IsolationLevel::Os);
201    /// ```
202    pub fn builder() -> ConfigBuilder {
203        ConfigBuilder::default()
204    }
205
206    /// Converts to the internal `mimobox_core::SandboxConfig`.
207    pub(crate) fn to_sandbox_config(&self) -> SandboxConfig {
208        let deny_network = resolve_deny_network(&self.network);
209
210        let mut config = SandboxConfig::default();
211        config.fs_readonly = self.fs_readonly.clone();
212        config.fs_readwrite = self.fs_readwrite.clone();
213        config.deny_network = deny_network;
214        config.memory_limit_mb = self.memory_limit_mb;
215        config.cpu_quota_us = self.cpu_quota_us;
216        config.cpu_period_us = self.cpu_period_us;
217        config.timeout_secs = self.timeout.map(round_up_timeout_secs);
218        config.seccomp_profile = resolve_seccomp_profile(deny_network, self.allow_fork);
219        config.allow_fork = self.allow_fork;
220        config.allowed_http_domains = resolve_allowed_http_domains(self);
221        config
222    }
223
224    #[cfg(feature = "vm")]
225    #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
226    pub(crate) fn to_microvm_config(&self) -> Result<mimobox_vm::MicrovmConfig, SdkError> {
227        let defaults = mimobox_vm::MicrovmConfig::default();
228        let memory_mb = resolve_vm_memory_mb(self)?;
229
230        Ok(mimobox_vm::MicrovmConfig {
231            vcpu_count: self.vm_vcpu_count,
232            memory_mb,
233            cpu_quota_us: self.cpu_quota_us,
234            kernel_path: self
235                .kernel_path
236                .clone()
237                .unwrap_or_else(|| defaults.kernel_path.clone()),
238            rootfs_path: self
239                .rootfs_path
240                .clone()
241                .unwrap_or_else(|| defaults.rootfs_path.clone()),
242        })
243    }
244}
245
246fn resolve_deny_network(network: &NetworkPolicy) -> bool {
247    match network {
248        NetworkPolicy::DenyAll => true,
249        NetworkPolicy::AllowDomains(_) => true,
250        NetworkPolicy::AllowAll => false,
251    }
252}
253
254fn resolve_allowed_http_domains(config: &Config) -> Vec<String> {
255    let mut domains = config.allowed_http_domains.clone();
256    if let NetworkPolicy::AllowDomains(network_domains) = &config.network {
257        for domain in network_domains {
258            if !domains.contains(domain) {
259                domains.push(domain.clone());
260            }
261        }
262    }
263    domains
264}
265
266fn round_up_timeout_secs(timeout: Duration) -> u64 {
267    let millis = timeout.as_millis();
268    let seconds = millis.div_ceil(1_000);
269    u64::try_from(seconds).unwrap_or(u64::MAX)
270}
271
272fn resolve_seccomp_profile(deny_network: bool, allow_fork: bool) -> SeccompProfile {
273    match (deny_network, allow_fork) {
274        (true, true) => SeccompProfile::EssentialWithFork,
275        (true, false) => SeccompProfile::Essential,
276        (false, true) => SeccompProfile::NetworkWithFork,
277        (false, false) => SeccompProfile::Network,
278    }
279}
280
281#[cfg(feature = "vm")]
282fn resolve_vm_memory_mb(config: &Config) -> Result<u32, SdkError> {
283    let requested_memory_mb = u64::from(config.vm_memory_mb);
284    let effective_memory_mb = match config.memory_limit_mb {
285        Some(memory_limit_mb) => requested_memory_mb.min(memory_limit_mb),
286        None => requested_memory_mb,
287    };
288
289    u32::try_from(effective_memory_mb).map_err(|_| {
290        SdkError::Config(format!(
291            "microVM guest memory 超出 u32 范围: {effective_memory_mb} MB"
292        ))
293    })
294}
295
296/// Fluent builder for constructing [`Config`] instances.
297///
298/// All methods consume and return `self`, enabling method chaining.
299/// Call [`build()`](ConfigBuilder::build) to produce the final `Config`.
300///
301/// # Examples
302///
303/// ```
304/// use mimobox_sdk::{Config, IsolationLevel, NetworkPolicy};
305/// use std::time::Duration;
306///
307/// let config = Config::builder()
308///     .isolation(IsolationLevel::MicroVm)
309///     .timeout(Duration::from_secs(60))
310///     .memory_limit_mb(256)
311///     .allowed_http_domains(["api.openai.com"])
312///     .build();
313///
314/// assert_eq!(config.isolation, IsolationLevel::MicroVm);
315/// ```
316#[derive(Debug, Clone, Default)]
317pub struct ConfigBuilder {
318    inner: Config,
319}
320
321impl ConfigBuilder {
322    /// Set the isolation level selection strategy.
323    ///
324    /// # Examples
325    ///
326    /// ```
327    /// use mimobox_sdk::{Config, IsolationLevel};
328    ///
329    /// let config = Config::builder()
330    ///     .isolation(IsolationLevel::Wasm)
331    ///     .build();
332    ///
333    /// assert_eq!(config.isolation, IsolationLevel::Wasm);
334    /// ```
335    pub fn isolation(mut self, level: IsolationLevel) -> Self {
336        self.inner.isolation = level;
337        self
338    }
339
340    /// Set the trust level, affecting auto-routing decisions.
341    ///
342    /// When set to `TrustLevel::Untrusted`, the SDK will fail-closed if the
343    /// microVM backend is not available, rather than silently downgrading.
344    ///
345    /// # Examples
346    ///
347    /// ```
348    /// use mimobox_sdk::{Config, TrustLevel};
349    ///
350    /// let config = Config::builder()
351    ///     .trust_level(TrustLevel::Untrusted)
352    ///     .build();
353    ///
354    /// assert_eq!(config.trust_level, TrustLevel::Untrusted);
355    /// ```
356    pub fn trust_level(mut self, level: TrustLevel) -> Self {
357        self.inner.trust_level = level;
358        self
359    }
360
361    /// Set the network access policy.
362    ///
363    /// # Examples
364    ///
365    /// ```
366    /// use mimobox_sdk::{Config, NetworkPolicy};
367    ///
368    /// let config = Config::builder()
369    ///     .network(NetworkPolicy::AllowDomains(vec!["api.openai.com".to_string()]))
370    ///     .build();
371    ///
372    /// assert!(matches!(config.network, NetworkPolicy::AllowDomains(_)));
373    /// ```
374    pub fn network(mut self, policy: NetworkPolicy) -> Self {
375        self.inner.network = policy;
376        self
377    }
378
379    /// Set the default command execution timeout.
380    ///
381    /// Internally rounded up to whole seconds. For example, `1500ms` maps to `2s`.
382    ///
383    /// # Examples
384    ///
385    /// ```
386    /// use mimobox_sdk::Config;
387    /// use std::time::Duration;
388    ///
389    /// let config = Config::builder()
390    ///     .timeout(Duration::from_secs(10))
391    ///     .build();
392    ///
393    /// assert_eq!(config.timeout, Some(Duration::from_secs(10)));
394    /// ```
395    pub fn timeout(mut self, timeout: Duration) -> Self {
396        self.inner.timeout = Some(timeout);
397        self
398    }
399
400    /// Set the memory limit in MiB.
401    ///
402    /// For the microVM backend, the effective guest memory is
403    /// `min(memory_limit_mb, vm_memory_mb)`.
404    ///
405    /// # Examples
406    ///
407    /// ```
408    /// use mimobox_sdk::Config;
409    ///
410    /// let config = Config::builder()
411    ///     .memory_limit_mb(256)
412    ///     .build();
413    ///
414    /// assert_eq!(config.memory_limit_mb, Some(256));
415    /// ```
416    pub fn memory_limit_mb(mut self, mb: u64) -> Self {
417        self.inner.memory_limit_mb = Some(mb);
418        self
419    }
420
421    /// Set the CPU time quota in microseconds.
422    ///
423    /// Linux OS backend maps this to cgroup v2 `cpu.max`; microVM stores the
424    /// value for backend-specific enforcement. Other backends ignore it.
425    pub fn cpu_quota(mut self, quota_us: u64) -> Self {
426        self.inner.cpu_quota_us = Some(quota_us);
427        self
428    }
429
430    /// Set the CPU period in microseconds.
431    pub fn cpu_period(mut self, period_us: u64) -> Self {
432        self.inner.cpu_period_us = period_us;
433        self
434    }
435
436    /// Set the read-only mount paths.
437    ///
438    /// Paths listed here are mounted read-only inside the sandbox.
439    ///
440    /// # Examples
441    ///
442    /// ```
443    /// use mimobox_sdk::Config;
444    ///
445    /// let config = Config::builder()
446    ///     .fs_readonly(["/usr", "/lib"])
447    ///     .build();
448    ///
449    /// assert_eq!(config.fs_readonly.len(), 2);
450    /// ```
451    pub fn fs_readonly(mut self, paths: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
452        self.inner.fs_readonly = paths.into_iter().map(Into::into).collect();
453        self
454    }
455
456    /// Set the read-write mount paths.
457    ///
458    /// # Examples
459    ///
460    /// ```
461    /// use mimobox_sdk::Config;
462    ///
463    /// let config = Config::builder()
464    ///     .fs_readwrite(["/tmp", "/workspace"])
465    ///     .build();
466    ///
467    /// assert_eq!(config.fs_readwrite.len(), 2);
468    /// ```
469    pub fn fs_readwrite(mut self, paths: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
470        self.inner.fs_readwrite = paths.into_iter().map(Into::into).collect();
471        self
472    }
473
474    /// Set whether child process creation (fork/clone) is allowed inside the sandbox.
475    ///
476    /// Default is `false`. Set to `true` for shell or interpreter workloads.
477    ///
478    /// # Examples
479    ///
480    /// ```
481    /// use mimobox_sdk::Config;
482    ///
483    /// let config = Config::builder()
484    ///     .allow_fork(true)
485    ///     .build();
486    ///
487    /// assert!(config.allow_fork);
488    /// ```
489    pub fn allow_fork(mut self, allow: bool) -> Self {
490        self.inner.allow_fork = allow;
491        self
492    }
493
494    /// Set the HTTP proxy domain whitelist.
495    ///
496    /// Supports glob patterns like `*.openai.com`. Combined with
497    /// `NetworkPolicy::AllowDomains` without duplicates.
498    ///
499    /// # Examples
500    ///
501    /// ```
502    /// use mimobox_sdk::Config;
503    ///
504    /// let config = Config::builder()
505    ///     .allowed_http_domains(["api.openai.com", "*.openai.com"])
506    ///     .build();
507    ///
508    /// assert_eq!(config.allowed_http_domains.len(), 2);
509    /// ```
510    pub fn allowed_http_domains(
511        mut self,
512        domains: impl IntoIterator<Item = impl Into<String>>,
513    ) -> Self {
514        self.inner.allowed_http_domains = domains.into_iter().map(Into::into).collect();
515        self
516    }
517
518    /// Set the microVM vCPU count.
519    ///
520    /// Only affects the microVM backend. Default is `1`.
521    ///
522    /// # Examples
523    ///
524    /// ```
525    /// use mimobox_sdk::Config;
526    ///
527    /// let config = Config::builder()
528    ///     .vm_vcpu_count(4)
529    ///     .build();
530    ///
531    /// assert_eq!(config.vm_vcpu_count, 4);
532    /// ```
533    pub fn vm_vcpu_count(mut self, count: u8) -> Self {
534        self.inner.vm_vcpu_count = count;
535        self
536    }
537
538    /// Set the microVM guest memory size in MiB.
539    ///
540    /// Capped by `memory_limit_mb` if set. Default is `256` MiB.
541    ///
542    /// # Examples
543    ///
544    /// ```
545    /// use mimobox_sdk::Config;
546    ///
547    /// let config = Config::builder()
548    ///     .vm_memory_mb(512)
549    ///     .build();
550    ///
551    /// assert_eq!(config.vm_memory_mb, 512);
552    /// ```
553    pub fn vm_memory_mb(mut self, mb: u32) -> Self {
554        self.inner.vm_memory_mb = mb;
555        self
556    }
557
558    /// Set the microVM kernel image path.
559    ///
560    /// If unset, falls back to `$VM_ASSETS_DIR/vmlinux` or `~/.mimobox/assets/vmlinux`.
561    ///
562    /// # Examples
563    ///
564    /// ```
565    /// use mimobox_sdk::Config;
566    ///
567    /// let config = Config::builder()
568    ///     .kernel_path("/opt/mimobox/vmlinux")
569    ///     .build();
570    ///
571    /// assert_eq!(config.kernel_path, Some(std::path::PathBuf::from("/opt/mimobox/vmlinux")));
572    /// ```
573    pub fn kernel_path(mut self, path: impl Into<PathBuf>) -> Self {
574        self.inner.kernel_path = Some(path.into());
575        self
576    }
577
578    /// Set the microVM rootfs path.
579    ///
580    /// If unset, falls back to `$VM_ASSETS_DIR/rootfs.cpio.gz` or `~/.mimobox/assets/rootfs.cpio.gz`.
581    ///
582    /// # Examples
583    ///
584    /// ```
585    /// use mimobox_sdk::Config;
586    ///
587    /// let config = Config::builder()
588    ///     .rootfs_path("/opt/mimobox/rootfs.cpio.gz")
589    ///     .build();
590    ///
591    /// assert_eq!(config.rootfs_path, Some(std::path::PathBuf::from("/opt/mimobox/rootfs.cpio.gz")));
592    /// ```
593    pub fn rootfs_path(mut self, path: impl Into<PathBuf>) -> Self {
594        self.inner.rootfs_path = Some(path.into());
595        self
596    }
597
598    /// Remove the default timeout, allowing commands to run indefinitely.
599    ///
600    /// # Examples
601    ///
602    /// ```
603    /// use mimobox_sdk::Config;
604    ///
605    /// let config = Config::builder()
606    ///     .no_timeout()
607    ///     .build();
608    ///
609    /// assert_eq!(config.timeout, None);
610    /// ```
611    pub fn no_timeout(mut self) -> Self {
612        self.inner.timeout = None;
613        self
614    }
615
616    /// Produce the final `Config`.
617    ///
618    /// # Examples
619    ///
620    /// ```
621    /// use mimobox_sdk::Config;
622    ///
623    /// let config = Config::builder().build();
624    /// assert_eq!(config.isolation, mimobox_sdk::IsolationLevel::Auto);
625    /// ```
626    pub fn build(self) -> Config {
627        self.inner
628    }
629}
630
631#[cfg(test)]
632mod tests {
633    use super::*;
634
635    #[test]
636    fn default_config_keeps_microvm_artifact_paths_unset() {
637        let config = Config::default();
638
639        assert_eq!(config.vm_vcpu_count, 1);
640        assert_eq!(config.vm_memory_mb, 256);
641        assert_eq!(config.kernel_path, None);
642        assert_eq!(config.rootfs_path, None);
643    }
644
645    #[test]
646    fn builder_can_override_microvm_resource_config() {
647        let config = Config::builder().vm_vcpu_count(4).vm_memory_mb(768).build();
648
649        assert_eq!(config.vm_vcpu_count, 4);
650        assert_eq!(config.vm_memory_mb, 768);
651    }
652
653    #[test]
654    fn builder_can_override_microvm_artifact_paths() {
655        let config = Config::builder()
656            .kernel_path("/opt/mimobox/vmlinux")
657            .rootfs_path("/opt/mimobox/rootfs.cpio.gz")
658            .build();
659
660        assert_eq!(
661            config.kernel_path,
662            Some(PathBuf::from("/opt/mimobox/vmlinux"))
663        );
664        assert_eq!(
665            config.rootfs_path,
666            Some(PathBuf::from("/opt/mimobox/rootfs.cpio.gz"))
667        );
668    }
669
670    #[test]
671    fn allow_domains_keep_direct_network_denied_and_forward_whitelist() {
672        let config = Config::builder()
673            .network(NetworkPolicy::AllowDomains(vec!["example.com".to_string()]))
674            .build();
675
676        assert!(config.to_sandbox_config().deny_network);
677        assert_eq!(
678            config.to_sandbox_config().allowed_http_domains,
679            vec!["example.com".to_string()]
680        );
681    }
682
683    #[test]
684    fn allow_all_opens_network_and_uses_network_seccomp_profile() {
685        let config = Config::builder()
686            .network(NetworkPolicy::AllowAll)
687            .allow_fork(true)
688            .build();
689        let sandbox_config = config.to_sandbox_config();
690
691        assert!(!sandbox_config.deny_network);
692        assert!(matches!(
693            sandbox_config.seccomp_profile,
694            SeccompProfile::NetworkWithFork
695        ));
696    }
697
698    #[test]
699    fn timeout_rounds_up_instead_of_truncating_subsecond_precision() {
700        let config = Config::builder()
701            .timeout(Duration::from_millis(1_500))
702            .build();
703        assert_eq!(config.to_sandbox_config().timeout_secs, Some(2));
704
705        let config = Config::builder().timeout(Duration::from_millis(1)).build();
706        assert_eq!(config.to_sandbox_config().timeout_secs, Some(1));
707    }
708
709    #[test]
710    fn explicit_allowed_http_domains_are_forwarded_to_sandbox_config() {
711        let config = Config::builder()
712            .allowed_http_domains(["api.openai.com", "*.openai.com"])
713            .build();
714        let sandbox_config = config.to_sandbox_config();
715
716        assert_eq!(
717            sandbox_config.allowed_http_domains,
718            vec!["api.openai.com".to_string(), "*.openai.com".to_string()]
719        );
720    }
721
722    #[test]
723    fn allow_domains_merge_with_explicit_http_whitelist_without_duplicates() {
724        let config = Config::builder()
725            .network(NetworkPolicy::AllowDomains(vec![
726                "api.openai.com".to_string(),
727                "example.com".to_string(),
728            ]))
729            .allowed_http_domains(["api.openai.com", "*.openai.com"])
730            .build();
731        let sandbox_config = config.to_sandbox_config();
732
733        assert_eq!(
734            sandbox_config.allowed_http_domains,
735            vec![
736                "api.openai.com".to_string(),
737                "*.openai.com".to_string(),
738                "example.com".to_string()
739            ]
740        );
741    }
742
743    #[cfg(feature = "vm")]
744    #[test]
745    fn microvm_config_uses_default_artifact_paths_when_not_overridden() {
746        let config = Config::default();
747        let microvm_config = config.to_microvm_config().expect("构造 microVM 配置失败");
748        let defaults = mimobox_vm::MicrovmConfig::default();
749
750        assert_eq!(microvm_config.kernel_path, defaults.kernel_path);
751        assert_eq!(microvm_config.rootfs_path, defaults.rootfs_path);
752    }
753
754    #[cfg(feature = "vm")]
755    #[test]
756    fn microvm_config_applies_resource_and_artifact_overrides() {
757        let config = Config::builder()
758            .vm_vcpu_count(4)
759            .vm_memory_mb(768)
760            .memory_limit_mb(1024)
761            .cpu_quota(50_000)
762            .cpu_period(100_000)
763            .kernel_path("/srv/mimobox/vmlinux")
764            .rootfs_path("/srv/mimobox/rootfs.cpio.gz")
765            .build();
766        let microvm_config = config.to_microvm_config().expect("构造 microVM 配置失败");
767
768        assert_eq!(microvm_config.vcpu_count, 4);
769        assert_eq!(microvm_config.memory_mb, 768);
770        assert_eq!(microvm_config.cpu_quota_us, Some(50_000));
771        assert_eq!(config.to_sandbox_config().cpu_period_us, 100_000);
772        assert_eq!(
773            microvm_config.kernel_path,
774            PathBuf::from("/srv/mimobox/vmlinux")
775        );
776        assert_eq!(
777            microvm_config.rootfs_path,
778            PathBuf::from("/srv/mimobox/rootfs.cpio.gz")
779        );
780    }
781
782    #[cfg(feature = "vm")]
783    #[test]
784    fn microvm_config_caps_vm_memory_with_memory_limit() {
785        let config = Config::builder()
786            .vm_memory_mb(768)
787            .memory_limit_mb(256)
788            .build();
789        let microvm_config = config.to_microvm_config().expect("构造 microVM 配置失败");
790
791        assert_eq!(microvm_config.memory_mb, 256);
792    }
793
794    #[cfg(feature = "vm")]
795    #[test]
796    fn microvm_config_ignores_out_of_range_memory_limit_when_vm_memory_is_lower() {
797        let config = Config::builder()
798            .vm_memory_mb(768)
799            .memory_limit_mb(u64::MAX)
800            .build();
801        let microvm_config = config.to_microvm_config().expect("构造 microVM 配置失败");
802
803        assert_eq!(microvm_config.memory_mb, 768);
804    }
805}