mustardscript 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1579 -0
- package/Cargo.toml +40 -0
- package/LICENSE +201 -0
- package/README.md +828 -0
- package/SECURITY.md +34 -0
- package/crates/mustard/Cargo.toml +31 -0
- package/crates/mustard/src/cancellation.rs +28 -0
- package/crates/mustard/src/diagnostic.rs +145 -0
- package/crates/mustard/src/ir.rs +435 -0
- package/crates/mustard/src/lib.rs +21 -0
- package/crates/mustard/src/limits.rs +22 -0
- package/crates/mustard/src/parser/expressions.rs +723 -0
- package/crates/mustard/src/parser/mod.rs +115 -0
- package/crates/mustard/src/parser/operators.rs +105 -0
- package/crates/mustard/src/parser/patterns.rs +123 -0
- package/crates/mustard/src/parser/scope.rs +107 -0
- package/crates/mustard/src/parser/statements.rs +298 -0
- package/crates/mustard/src/parser/tests/acceptance.rs +339 -0
- package/crates/mustard/src/parser/tests/mod.rs +2 -0
- package/crates/mustard/src/parser/tests/rejections.rs +107 -0
- package/crates/mustard/src/runtime/accounting.rs +613 -0
- package/crates/mustard/src/runtime/api.rs +192 -0
- package/crates/mustard/src/runtime/async_runtime/mod.rs +5 -0
- package/crates/mustard/src/runtime/async_runtime/promises.rs +246 -0
- package/crates/mustard/src/runtime/async_runtime/reactions.rs +400 -0
- package/crates/mustard/src/runtime/async_runtime/scheduler.rs +224 -0
- package/crates/mustard/src/runtime/builtins/arrays.rs +1205 -0
- package/crates/mustard/src/runtime/builtins/collections.rs +573 -0
- package/crates/mustard/src/runtime/builtins/install.rs +501 -0
- package/crates/mustard/src/runtime/builtins/intl.rs +553 -0
- package/crates/mustard/src/runtime/builtins/mod.rs +25 -0
- package/crates/mustard/src/runtime/builtins/objects.rs +405 -0
- package/crates/mustard/src/runtime/builtins/primitives.rs +859 -0
- package/crates/mustard/src/runtime/builtins/promises.rs +335 -0
- package/crates/mustard/src/runtime/builtins/regexp.rs +356 -0
- package/crates/mustard/src/runtime/builtins/strings.rs +803 -0
- package/crates/mustard/src/runtime/builtins/support.rs +561 -0
- package/crates/mustard/src/runtime/bytecode.rs +123 -0
- package/crates/mustard/src/runtime/compiler/assignments.rs +690 -0
- package/crates/mustard/src/runtime/compiler/bindings.rs +92 -0
- package/crates/mustard/src/runtime/compiler/context.rs +46 -0
- package/crates/mustard/src/runtime/compiler/control.rs +342 -0
- package/crates/mustard/src/runtime/compiler/expressions.rs +372 -0
- package/crates/mustard/src/runtime/compiler/mod.rs +173 -0
- package/crates/mustard/src/runtime/compiler/statements.rs +459 -0
- package/crates/mustard/src/runtime/conversions/boundary.rs +293 -0
- package/crates/mustard/src/runtime/conversions/coercions.rs +217 -0
- package/crates/mustard/src/runtime/conversions/errors.rs +118 -0
- package/crates/mustard/src/runtime/conversions/mod.rs +14 -0
- package/crates/mustard/src/runtime/conversions/operators.rs +334 -0
- package/crates/mustard/src/runtime/env.rs +355 -0
- package/crates/mustard/src/runtime/exceptions.rs +377 -0
- package/crates/mustard/src/runtime/gc.rs +595 -0
- package/crates/mustard/src/runtime/mod.rs +318 -0
- package/crates/mustard/src/runtime/properties.rs +1762 -0
- package/crates/mustard/src/runtime/serialization.rs +127 -0
- package/crates/mustard/src/runtime/shared.rs +108 -0
- package/crates/mustard/src/runtime/snapshot_validation_tests.rs +93 -0
- package/crates/mustard/src/runtime/state.rs +652 -0
- package/crates/mustard/src/runtime/tests/async_host.rs +104 -0
- package/crates/mustard/src/runtime/tests/collections.rs +50 -0
- package/crates/mustard/src/runtime/tests/diagnostics.rs +36 -0
- package/crates/mustard/src/runtime/tests/exceptions.rs +122 -0
- package/crates/mustard/src/runtime/tests/execution.rs +553 -0
- package/crates/mustard/src/runtime/tests/gc.rs +533 -0
- package/crates/mustard/src/runtime/tests/mod.rs +56 -0
- package/crates/mustard/src/runtime/tests/serialization.rs +170 -0
- package/crates/mustard/src/runtime/validation/bytecode.rs +484 -0
- package/crates/mustard/src/runtime/validation/mod.rs +14 -0
- package/crates/mustard/src/runtime/validation/policy.rs +94 -0
- package/crates/mustard/src/runtime/validation/snapshot.rs +406 -0
- package/crates/mustard/src/runtime/validation/walk.rs +206 -0
- package/crates/mustard/src/runtime/vm.rs +1016 -0
- package/crates/mustard/src/span.rs +22 -0
- package/crates/mustard/src/structured.rs +107 -0
- package/crates/mustard-bridge/Cargo.toml +17 -0
- package/crates/mustard-bridge/src/codec.rs +46 -0
- package/crates/mustard-bridge/src/dto.rs +99 -0
- package/crates/mustard-bridge/src/lib.rs +12 -0
- package/crates/mustard-bridge/src/operations.rs +142 -0
- package/crates/mustard-node/Cargo.toml +24 -0
- package/crates/mustard-node/build.rs +3 -0
- package/crates/mustard-node/src/lib.rs +236 -0
- package/crates/mustard-sidecar/Cargo.toml +21 -0
- package/crates/mustard-sidecar/src/lib.rs +134 -0
- package/crates/mustard-sidecar/src/main.rs +36 -0
- package/dist/index.js +20 -0
- package/dist/install.js +117 -0
- package/dist/lib/cancellation.js +124 -0
- package/dist/lib/errors.js +46 -0
- package/dist/lib/executor.js +555 -0
- package/dist/lib/policy.js +292 -0
- package/dist/lib/progress.js +356 -0
- package/dist/lib/runtime.js +109 -0
- package/dist/lib/structured.js +286 -0
- package/dist/native-loader.js +227 -0
- package/index.d.ts +23 -0
- package/mustard.d.ts +220 -0
- package/package.json +97 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
use serde::{Deserialize, Serialize};
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
4
|
+
pub struct SourceSpan {
|
|
5
|
+
pub start: u32,
|
|
6
|
+
pub end: u32,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
impl SourceSpan {
|
|
10
|
+
pub const fn new(start: u32, end: u32) -> Self {
|
|
11
|
+
Self { start, end }
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl From<oxc_span::Span> for SourceSpan {
|
|
16
|
+
fn from(value: oxc_span::Span) -> Self {
|
|
17
|
+
Self {
|
|
18
|
+
start: value.start,
|
|
19
|
+
end: value.end,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
use indexmap::IndexMap;
|
|
2
|
+
use serde::{Deserialize, Serialize};
|
|
3
|
+
|
|
4
|
+
use crate::{
|
|
5
|
+
diagnostic::{MustardError, MustardResult},
|
|
6
|
+
span::SourceSpan,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
10
|
+
pub enum StructuredNumber {
|
|
11
|
+
Finite(f64),
|
|
12
|
+
NaN,
|
|
13
|
+
Infinity,
|
|
14
|
+
NegInfinity,
|
|
15
|
+
NegZero,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl StructuredNumber {
|
|
19
|
+
pub fn from_f64(value: f64) -> Self {
|
|
20
|
+
if value.is_nan() {
|
|
21
|
+
Self::NaN
|
|
22
|
+
} else if value == 0.0f64 && value.is_sign_negative() {
|
|
23
|
+
Self::NegZero
|
|
24
|
+
} else if value == f64::INFINITY {
|
|
25
|
+
Self::Infinity
|
|
26
|
+
} else if value == f64::NEG_INFINITY {
|
|
27
|
+
Self::NegInfinity
|
|
28
|
+
} else {
|
|
29
|
+
Self::Finite(value)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn to_f64(&self) -> f64 {
|
|
34
|
+
match self {
|
|
35
|
+
Self::Finite(value) => *value,
|
|
36
|
+
Self::NaN => f64::NAN,
|
|
37
|
+
Self::Infinity => f64::INFINITY,
|
|
38
|
+
Self::NegInfinity => f64::NEG_INFINITY,
|
|
39
|
+
Self::NegZero => -0.0,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
45
|
+
pub enum StructuredValue {
|
|
46
|
+
Undefined,
|
|
47
|
+
Null,
|
|
48
|
+
Hole,
|
|
49
|
+
Bool(bool),
|
|
50
|
+
String(String),
|
|
51
|
+
Number(StructuredNumber),
|
|
52
|
+
Array(Vec<StructuredValue>),
|
|
53
|
+
Object(IndexMap<String, StructuredValue>),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl StructuredValue {
|
|
57
|
+
pub fn validate_plain_object(
|
|
58
|
+
prototype_is_plain: bool,
|
|
59
|
+
has_accessors: bool,
|
|
60
|
+
has_cycles: bool,
|
|
61
|
+
span: Option<SourceSpan>,
|
|
62
|
+
) -> MustardResult<()> {
|
|
63
|
+
if !prototype_is_plain {
|
|
64
|
+
return Err(MustardError::validation(
|
|
65
|
+
"host objects with custom prototypes cannot cross the host boundary",
|
|
66
|
+
span,
|
|
67
|
+
));
|
|
68
|
+
}
|
|
69
|
+
if has_accessors {
|
|
70
|
+
return Err(MustardError::validation(
|
|
71
|
+
"host objects with accessors cannot cross the host boundary",
|
|
72
|
+
span,
|
|
73
|
+
));
|
|
74
|
+
}
|
|
75
|
+
if has_cycles {
|
|
76
|
+
return Err(MustardError::validation(
|
|
77
|
+
"cyclic values cannot cross the host boundary",
|
|
78
|
+
span,
|
|
79
|
+
));
|
|
80
|
+
}
|
|
81
|
+
Ok(())
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
impl From<bool> for StructuredValue {
|
|
86
|
+
fn from(value: bool) -> Self {
|
|
87
|
+
Self::Bool(value)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
impl From<&str> for StructuredValue {
|
|
92
|
+
fn from(value: &str) -> Self {
|
|
93
|
+
Self::String(value.to_string())
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
impl From<String> for StructuredValue {
|
|
98
|
+
fn from(value: String) -> Self {
|
|
99
|
+
Self::String(value)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
impl From<f64> for StructuredValue {
|
|
104
|
+
fn from(value: f64) -> Self {
|
|
105
|
+
Self::Number(StructuredNumber::from_f64(value))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "mustard-bridge"
|
|
3
|
+
version.workspace = true
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
license.workspace = true
|
|
6
|
+
authors.workspace = true
|
|
7
|
+
repository.workspace = true
|
|
8
|
+
description = "Shared bridge DTOs and operations for MustardScript adapters"
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
anyhow.workspace = true
|
|
12
|
+
base64 = "0.22.1"
|
|
13
|
+
hmac.workspace = true
|
|
14
|
+
mustard = { path = "../mustard" }
|
|
15
|
+
serde.workspace = true
|
|
16
|
+
serde_json.workspace = true
|
|
17
|
+
sha2.workspace = true
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
|
3
|
+
use mustard::{BytecodeProgram, ExecutionStep, dump_snapshot, load_program};
|
|
4
|
+
use serde::{Serialize, de::DeserializeOwned};
|
|
5
|
+
|
|
6
|
+
use crate::dto::StepDto;
|
|
7
|
+
|
|
8
|
+
pub fn parse_json<T: DeserializeOwned>(value: &str) -> Result<T> {
|
|
9
|
+
serde_json::from_str(value).map_err(Into::into)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
pub fn encode_json<T: Serialize>(value: &T) -> Result<String> {
|
|
13
|
+
serde_json::to_string(value).map_err(Into::into)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn encode_step(step: ExecutionStep) -> Result<StepDto> {
|
|
17
|
+
Ok(match step {
|
|
18
|
+
ExecutionStep::Completed(value) => StepDto::Completed { value },
|
|
19
|
+
ExecutionStep::Suspended(suspension) => StepDto::Suspended {
|
|
20
|
+
capability: suspension.capability,
|
|
21
|
+
args: suspension.args,
|
|
22
|
+
snapshot_base64: STANDARD.encode(dump_snapshot(&suspension.snapshot)?),
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub fn encode_step_json(step: ExecutionStep) -> Result<String> {
|
|
28
|
+
encode_json(&encode_step(step)?)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pub fn decode_program(bytes: &[u8]) -> Result<BytecodeProgram> {
|
|
32
|
+
load_program(bytes).map_err(Into::into)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub fn decode_base64(value: &str) -> Result<Vec<u8>> {
|
|
36
|
+
STANDARD.decode(value).map_err(Into::into)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub fn decode_program_base64(value: &str) -> Result<BytecodeProgram> {
|
|
40
|
+
let bytes = decode_base64(value)?;
|
|
41
|
+
decode_program(&bytes)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pub fn encode_bytes_base64(bytes: &[u8]) -> String {
|
|
45
|
+
STANDARD.encode(bytes)
|
|
46
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
use std::collections::BTreeMap;
|
|
2
|
+
|
|
3
|
+
use anyhow::{Result, anyhow};
|
|
4
|
+
use mustard::{HostError, ResumePayload, RuntimeLimits, SnapshotPolicy, StructuredValue};
|
|
5
|
+
use serde::{Deserialize, Serialize};
|
|
6
|
+
|
|
7
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
8
|
+
pub struct StartOptionsDto {
|
|
9
|
+
#[serde(default)]
|
|
10
|
+
pub inputs: BTreeMap<String, StructuredValue>,
|
|
11
|
+
#[serde(default)]
|
|
12
|
+
pub capabilities: Vec<String>,
|
|
13
|
+
#[serde(default)]
|
|
14
|
+
pub limits: RuntimeLimitsDto,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[derive(Debug, Default, Serialize, Deserialize)]
|
|
18
|
+
pub struct RuntimeLimitsDto {
|
|
19
|
+
pub instruction_budget: Option<usize>,
|
|
20
|
+
pub heap_limit_bytes: Option<usize>,
|
|
21
|
+
pub allocation_budget: Option<usize>,
|
|
22
|
+
pub call_depth_limit: Option<usize>,
|
|
23
|
+
pub max_outstanding_host_calls: Option<usize>,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
impl RuntimeLimitsDto {
|
|
27
|
+
pub fn into_runtime_limits(self) -> RuntimeLimits {
|
|
28
|
+
let defaults = RuntimeLimits::default();
|
|
29
|
+
RuntimeLimits {
|
|
30
|
+
instruction_budget: self
|
|
31
|
+
.instruction_budget
|
|
32
|
+
.unwrap_or(defaults.instruction_budget),
|
|
33
|
+
heap_limit_bytes: self.heap_limit_bytes.unwrap_or(defaults.heap_limit_bytes),
|
|
34
|
+
allocation_budget: self.allocation_budget.unwrap_or(defaults.allocation_budget),
|
|
35
|
+
call_depth_limit: self.call_depth_limit.unwrap_or(defaults.call_depth_limit),
|
|
36
|
+
max_outstanding_host_calls: self
|
|
37
|
+
.max_outstanding_host_calls
|
|
38
|
+
.unwrap_or(defaults.max_outstanding_host_calls),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
44
|
+
pub struct SnapshotPolicyDto {
|
|
45
|
+
#[serde(default)]
|
|
46
|
+
pub capabilities: Vec<String>,
|
|
47
|
+
pub limits: Option<RuntimeLimitsDto>,
|
|
48
|
+
#[serde(default)]
|
|
49
|
+
pub snapshot_key_base64: Option<String>,
|
|
50
|
+
#[serde(default)]
|
|
51
|
+
pub snapshot_token: Option<String>,
|
|
52
|
+
#[serde(default)]
|
|
53
|
+
pub snapshot_id: Option<String>,
|
|
54
|
+
#[serde(default)]
|
|
55
|
+
pub snapshot_key_digest: Option<String>,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
impl SnapshotPolicyDto {
|
|
59
|
+
pub fn into_snapshot_policy(self) -> Result<SnapshotPolicy> {
|
|
60
|
+
let limits = self
|
|
61
|
+
.limits
|
|
62
|
+
.ok_or_else(|| anyhow!("raw snapshot restore requires explicit limits"))?;
|
|
63
|
+
Ok(SnapshotPolicy {
|
|
64
|
+
capabilities: self.capabilities,
|
|
65
|
+
limits: limits.into_runtime_limits(),
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
71
|
+
#[serde(tag = "type", rename_all = "snake_case")]
|
|
72
|
+
pub enum StepDto {
|
|
73
|
+
Completed {
|
|
74
|
+
value: StructuredValue,
|
|
75
|
+
},
|
|
76
|
+
Suspended {
|
|
77
|
+
capability: String,
|
|
78
|
+
args: Vec<StructuredValue>,
|
|
79
|
+
snapshot_base64: String,
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
84
|
+
#[serde(tag = "type", rename_all = "snake_case")]
|
|
85
|
+
pub enum ResumeDto {
|
|
86
|
+
Value { value: StructuredValue },
|
|
87
|
+
Error { error: HostError },
|
|
88
|
+
Cancelled,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
impl ResumeDto {
|
|
92
|
+
pub fn into_resume_payload(self) -> ResumePayload {
|
|
93
|
+
match self {
|
|
94
|
+
Self::Value { value } => ResumePayload::Value(value),
|
|
95
|
+
Self::Error { error } => ResumePayload::Error(error),
|
|
96
|
+
Self::Cancelled => ResumePayload::Cancelled,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
mod codec;
|
|
2
|
+
mod dto;
|
|
3
|
+
mod operations;
|
|
4
|
+
|
|
5
|
+
pub use codec::{
|
|
6
|
+
decode_base64, decode_program, decode_program_base64, encode_bytes_base64, encode_json,
|
|
7
|
+
encode_step, encode_step_json, parse_json,
|
|
8
|
+
};
|
|
9
|
+
pub use dto::{ResumeDto, RuntimeLimitsDto, SnapshotPolicyDto, StartOptionsDto, StepDto};
|
|
10
|
+
pub use operations::{
|
|
11
|
+
compile_program_bytes, inspect_snapshot_bytes, resume_program, start_program,
|
|
12
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
use anyhow::{Result, anyhow};
|
|
2
|
+
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
|
3
|
+
use hmac::{Hmac, Mac};
|
|
4
|
+
use mustard::{
|
|
5
|
+
BytecodeProgram, CancellationToken, ExecutionOptions, ResumeOptions, SnapshotInspection,
|
|
6
|
+
compile, dump_program, inspect_snapshot as inspect_loaded_snapshot, load_snapshot,
|
|
7
|
+
lower_to_bytecode, resume_with_options, start_bytecode,
|
|
8
|
+
};
|
|
9
|
+
use sha2::{Digest, Sha256};
|
|
10
|
+
|
|
11
|
+
use crate::{
|
|
12
|
+
codec::encode_step,
|
|
13
|
+
dto::{ResumeDto, SnapshotPolicyDto, StartOptionsDto, StepDto},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type HmacSha256 = Hmac<Sha256>;
|
|
17
|
+
|
|
18
|
+
fn snapshot_identity_hex(snapshot: &[u8]) -> String {
|
|
19
|
+
let digest = Sha256::digest(snapshot);
|
|
20
|
+
let mut encoded = String::with_capacity(digest.len() * 2);
|
|
21
|
+
for byte in digest {
|
|
22
|
+
use std::fmt::Write as _;
|
|
23
|
+
let _ = write!(&mut encoded, "{byte:02x}");
|
|
24
|
+
}
|
|
25
|
+
encoded
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn snapshot_key_digest_hex(snapshot_key: &[u8]) -> String {
|
|
29
|
+
let digest = Sha256::digest(snapshot_key);
|
|
30
|
+
let mut encoded = String::with_capacity(digest.len() * 2);
|
|
31
|
+
for byte in digest {
|
|
32
|
+
use std::fmt::Write as _;
|
|
33
|
+
let _ = write!(&mut encoded, "{byte:02x}");
|
|
34
|
+
}
|
|
35
|
+
encoded
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn encode_snapshot_token(snapshot_id: &str, snapshot_key: &[u8]) -> Result<String> {
|
|
39
|
+
let mut mac =
|
|
40
|
+
HmacSha256::new_from_slice(snapshot_key).map_err(|_| anyhow!("invalid snapshot key"))?;
|
|
41
|
+
mac.update(snapshot_id.as_bytes());
|
|
42
|
+
let digest = mac.finalize().into_bytes();
|
|
43
|
+
let mut token = String::with_capacity(digest.len() * 2);
|
|
44
|
+
for byte in digest {
|
|
45
|
+
use std::fmt::Write as _;
|
|
46
|
+
let _ = write!(&mut token, "{byte:02x}");
|
|
47
|
+
}
|
|
48
|
+
Ok(token)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn assert_authenticated_snapshot(snapshot_bytes: &[u8], policy: &SnapshotPolicyDto) -> Result<()> {
|
|
52
|
+
let snapshot_id = policy
|
|
53
|
+
.snapshot_id
|
|
54
|
+
.as_deref()
|
|
55
|
+
.ok_or_else(|| anyhow!("raw snapshot restore requires snapshot_id"))?;
|
|
56
|
+
let snapshot_key_base64 = policy
|
|
57
|
+
.snapshot_key_base64
|
|
58
|
+
.as_deref()
|
|
59
|
+
.ok_or_else(|| anyhow!("raw snapshot restore requires snapshot_key_base64"))?;
|
|
60
|
+
let snapshot_token = policy
|
|
61
|
+
.snapshot_token
|
|
62
|
+
.as_deref()
|
|
63
|
+
.ok_or_else(|| anyhow!("raw snapshot restore requires snapshot_token"))?;
|
|
64
|
+
let snapshot_key_digest = policy
|
|
65
|
+
.snapshot_key_digest
|
|
66
|
+
.as_deref()
|
|
67
|
+
.ok_or_else(|| anyhow!("raw snapshot restore requires snapshot_key_digest"))?;
|
|
68
|
+
let snapshot_key = STANDARD
|
|
69
|
+
.decode(snapshot_key_base64)
|
|
70
|
+
.map_err(|_| anyhow!("snapshot_key_base64 must be valid base64"))?;
|
|
71
|
+
let expected_snapshot_id = snapshot_identity_hex(snapshot_bytes);
|
|
72
|
+
if expected_snapshot_id != snapshot_id {
|
|
73
|
+
return Err(anyhow!(
|
|
74
|
+
"raw snapshot restore rejected a tampered or unauthenticated snapshot"
|
|
75
|
+
));
|
|
76
|
+
}
|
|
77
|
+
if snapshot_key_digest_hex(&snapshot_key) != snapshot_key_digest {
|
|
78
|
+
return Err(anyhow!(
|
|
79
|
+
"raw snapshot restore rejected a mismatched snapshot key digest"
|
|
80
|
+
));
|
|
81
|
+
}
|
|
82
|
+
let expected = encode_snapshot_token(snapshot_id, &snapshot_key)?;
|
|
83
|
+
if expected != snapshot_token {
|
|
84
|
+
return Err(anyhow!(
|
|
85
|
+
"raw snapshot restore rejected a tampered or unauthenticated snapshot"
|
|
86
|
+
));
|
|
87
|
+
}
|
|
88
|
+
Ok(())
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pub fn compile_program_bytes(source: &str) -> Result<Vec<u8>> {
|
|
92
|
+
let parsed = compile(source)?;
|
|
93
|
+
let bytecode = lower_to_bytecode(&parsed)?;
|
|
94
|
+
dump_program(&bytecode).map_err(Into::into)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
pub fn start_program(
|
|
98
|
+
program: &BytecodeProgram,
|
|
99
|
+
options: StartOptionsDto,
|
|
100
|
+
cancellation_token: Option<CancellationToken>,
|
|
101
|
+
) -> Result<StepDto> {
|
|
102
|
+
let step = start_bytecode(
|
|
103
|
+
program,
|
|
104
|
+
ExecutionOptions {
|
|
105
|
+
inputs: options.inputs.into_iter().collect(),
|
|
106
|
+
capabilities: options.capabilities,
|
|
107
|
+
limits: options.limits.into_runtime_limits(),
|
|
108
|
+
cancellation_token,
|
|
109
|
+
},
|
|
110
|
+
)?;
|
|
111
|
+
encode_step(step)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pub fn inspect_snapshot_bytes(
|
|
115
|
+
snapshot_bytes: &[u8],
|
|
116
|
+
policy: SnapshotPolicyDto,
|
|
117
|
+
) -> Result<SnapshotInspection> {
|
|
118
|
+
assert_authenticated_snapshot(snapshot_bytes, &policy)?;
|
|
119
|
+
let mut snapshot = load_snapshot(snapshot_bytes)?;
|
|
120
|
+
let snapshot_policy = policy.into_snapshot_policy()?;
|
|
121
|
+
inspect_loaded_snapshot(&mut snapshot, snapshot_policy).map_err(Into::into)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pub fn resume_program(
|
|
125
|
+
snapshot_bytes: &[u8],
|
|
126
|
+
payload: ResumeDto,
|
|
127
|
+
policy: SnapshotPolicyDto,
|
|
128
|
+
cancellation_token: Option<CancellationToken>,
|
|
129
|
+
) -> Result<StepDto> {
|
|
130
|
+
assert_authenticated_snapshot(snapshot_bytes, &policy)?;
|
|
131
|
+
let snapshot = load_snapshot(snapshot_bytes)?;
|
|
132
|
+
let snapshot_policy = policy.into_snapshot_policy()?;
|
|
133
|
+
let step = resume_with_options(
|
|
134
|
+
snapshot,
|
|
135
|
+
payload.into_resume_payload(),
|
|
136
|
+
ResumeOptions {
|
|
137
|
+
cancellation_token,
|
|
138
|
+
snapshot_policy: Some(snapshot_policy),
|
|
139
|
+
},
|
|
140
|
+
)?;
|
|
141
|
+
encode_step(step)
|
|
142
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "mustard-node"
|
|
3
|
+
version.workspace = true
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
license.workspace = true
|
|
6
|
+
authors.workspace = true
|
|
7
|
+
repository.workspace = true
|
|
8
|
+
description = "Node-API addon for the MustardScript runtime"
|
|
9
|
+
|
|
10
|
+
[lib]
|
|
11
|
+
crate-type = ["cdylib"]
|
|
12
|
+
|
|
13
|
+
[dependencies]
|
|
14
|
+
base64 = "0.22.1"
|
|
15
|
+
mustard = { path = "../mustard" }
|
|
16
|
+
mustard-bridge = { path = "../mustard-bridge" }
|
|
17
|
+
hmac.workspace = true
|
|
18
|
+
napi.workspace = true
|
|
19
|
+
napi-derive.workspace = true
|
|
20
|
+
rand.workspace = true
|
|
21
|
+
sha2.workspace = true
|
|
22
|
+
|
|
23
|
+
[build-dependencies]
|
|
24
|
+
napi-build = "2.3.1"
|