1use crate::core::{self, PromiseState, Value};
8use crate::lang::data::Symbol;
9use crate::{hta, Runtime};
10use std::error::Error;
11use std::fmt;
12use std::rc::Rc;
13
14pub const MAX_INVOKE_HTA_RESULT_BYTES: usize = 256 * 1024;
15const MAX_PROMISE_UNWRAP_DEPTH: usize = 32;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum InvokeHtaError {
19 InvalidQualifiedVar,
20 MalformedInput(String),
21 NoncanonicalInput,
22 ArgumentsNotVector,
23 NamespaceMissing(String),
24 VarMissing(String),
25 VarNotCallable(String),
26 Execution(String),
27 PromiseRejected(String),
28 PromisePending,
29 PromiseDepthExceeded,
30 UnsupportedResult(String),
31 ResultTooLarge { actual: usize, maximum: usize },
32 SessionMissing(String),
33 BrokerClosed,
34 BrokerStopped,
35}
36
37impl InvokeHtaError {
38 pub const fn code(&self) -> &'static str {
39 match self {
40 Self::InvalidQualifiedVar => "invoke-hta/qualified-var-invalid",
41 Self::MalformedInput(_) => "invoke-hta/input-malformed",
42 Self::NoncanonicalInput => "invoke-hta/input-noncanonical",
43 Self::ArgumentsNotVector => "invoke-hta/arguments-not-vector",
44 Self::NamespaceMissing(_) => "invoke-hta/namespace-missing",
45 Self::VarMissing(_) => "invoke-hta/var-missing",
46 Self::VarNotCallable(_) => "invoke-hta/var-not-callable",
47 Self::Execution(_) => "invoke-hta/execution-failed",
48 Self::PromiseRejected(_) => "invoke-hta/promise-rejected",
49 Self::PromisePending => "invoke-hta/promise-pending",
50 Self::PromiseDepthExceeded => "invoke-hta/promise-depth-exceeded",
51 Self::UnsupportedResult(_) => "invoke-hta/result-unsupported",
52 Self::ResultTooLarge { .. } => "invoke-hta/result-too-large",
53 Self::SessionMissing(_) => "invoke-hta/session-missing",
54 Self::BrokerClosed => "invoke-hta/broker-closed",
55 Self::BrokerStopped => "invoke-hta/broker-stopped",
56 }
57 }
58}
59
60impl fmt::Display for InvokeHtaError {
61 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(formatter, "{}", self.code())?;
63 match self {
64 Self::MalformedInput(detail)
65 | Self::Execution(detail)
66 | Self::PromiseRejected(detail)
67 | Self::UnsupportedResult(detail) => write!(formatter, ": {detail}"),
68 Self::NamespaceMissing(namespace) => write!(formatter, ": {namespace}"),
69 Self::VarMissing(path) | Self::VarNotCallable(path) => {
70 write!(formatter, ": {path}")
71 }
72 Self::ResultTooLarge { actual, maximum } => {
73 write!(formatter, ": {actual} exceeds {maximum} bytes")
74 }
75 Self::SessionMissing(session) => write!(formatter, ": {session}"),
76 _ => Ok(()),
77 }
78 }
79}
80
81impl Error for InvokeHtaError {}
82
83impl Runtime {
84 pub fn invoke_hta(
85 &mut self,
86 qualified_var: &str,
87 arguments_hta: &[u8],
88 ) -> Result<Vec<u8>, InvokeHtaError> {
89 let (namespace_name, var_name) = split_qualified_var(qualified_var)?;
90 let decoded = match hta::decode_canonical(arguments_hta) {
91 Ok(value) => value,
92 Err(error) if error.starts_with("hta/value-noncanonical:") => {
93 return Err(InvokeHtaError::NoncanonicalInput)
94 }
95 Err(error) => return Err(InvokeHtaError::MalformedInput(error)),
96 };
97 let arguments = match decoded {
98 Value::Vector(values) => values.iter().cloned().collect::<Vec<_>>(),
99 _ => return Err(InvokeHtaError::ArgumentsNotVector),
100 };
101
102 let namespace = self
103 .namespace_registry
104 .find(namespace_name)
105 .ok_or_else(|| InvokeHtaError::NamespaceMissing(namespace_name.to_owned()))?;
106 let symbol = Symbol::parse(var_name);
107 let var = namespace
108 .resolve(&symbol)
109 .ok_or_else(|| InvokeHtaError::VarMissing(qualified_var.to_owned()))?;
110 let function = match var.deref_value() {
111 Value::Function(function) => function,
112 _ => return Err(InvokeHtaError::VarNotCallable(qualified_var.to_owned())),
113 };
114
115 let result = self
116 .invoke_loaded_function(function, arguments)
117 .map_err(InvokeHtaError::Execution)?;
118 encode_result(settle_result(result)?)
119 }
120
121 fn invoke_loaded_function(
122 &mut self,
123 function: Rc<core::Function>,
124 arguments: Vec<Value>,
125 ) -> Result<Value, String> {
126 let namespace_source = self.namespace_source();
127 core::with_capability_providers(
128 self.providers.file(),
129 self.providers.socket(),
130 self.providers.process(),
131 self.providers.kernel(),
132 || {
133 core::with_package_catalog(&self.package_catalog, || {
134 core::with_promise_provider(self.providers.promise(), || {
135 core::with_macros(self.macros.clone(), || {
136 core::with_namespace_registry(&self.namespace_registry, || {
137 core::with_namespace_source(namespace_source, || {
138 core::with_protocols(&self.protocols, || {
139 if let Some(handler) = &self.native_host_handler {
140 return core::with_host_calls(handler.clone(), || {
141 core::invoke_function_sync(function, arguments)
142 });
143 }
144 core::invoke_function_sync(function, arguments)
145 })
146 })
147 })
148 })
149 })
150 })
151 },
152 )
153 }
154}
155
156fn split_qualified_var(value: &str) -> Result<(&str, &str), InvokeHtaError> {
157 let Some((namespace, name)) = value.split_once('/') else {
158 return Err(InvokeHtaError::InvalidQualifiedVar);
159 };
160 if namespace.is_empty()
161 || name.is_empty()
162 || name.contains('/')
163 || value.bytes().any(|byte| byte.is_ascii_whitespace())
164 {
165 return Err(InvokeHtaError::InvalidQualifiedVar);
166 }
167 Ok((namespace, name))
168}
169
170fn settle_result(mut value: Value) -> Result<Value, InvokeHtaError> {
171 for _ in 0..MAX_PROMISE_UNWRAP_DEPTH {
172 let Value::Promise(promise) = value else {
173 return Ok(value);
174 };
175 value = match promise.wait_state() {
176 PromiseState::Fulfilled(value) => value,
177 PromiseState::Rejected(error) => {
178 return Err(InvokeHtaError::PromiseRejected(error.message().to_owned()))
179 }
180 PromiseState::Pending => return Err(InvokeHtaError::PromisePending),
181 };
182 }
183 Err(InvokeHtaError::PromiseDepthExceeded)
184}
185
186fn encode_result(result: Value) -> Result<Vec<u8>, InvokeHtaError> {
187 let encoded = hta::encode(&result).map_err(InvokeHtaError::UnsupportedResult)?;
188 if encoded.len() > MAX_INVOKE_HTA_RESULT_BYTES {
189 return Err(InvokeHtaError::ResultTooLarge {
190 actual: encoded.len(),
191 maximum: MAX_INVOKE_HTA_RESULT_BYTES,
192 });
193 }
194 Ok(encoded)
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn fulfilled_and_rejected_promises_are_typed() {
203 let fulfilled = core::Promise::new();
204 assert!(fulfilled.resolve(Value::Number(42)));
205 assert_eq!(
206 settle_result(Value::Promise(fulfilled)),
207 Ok(Value::Number(42))
208 );
209
210 let rejected = core::Promise::new();
211 assert!(rejected.reject("no"));
212 assert_eq!(
213 settle_result(Value::Promise(rejected)),
214 Err(InvokeHtaError::PromiseRejected("no".to_owned()))
215 );
216 }
217
218 #[test]
219 fn encoded_results_are_bounded() {
220 let result = Value::String("x".repeat(MAX_INVOKE_HTA_RESULT_BYTES));
221 assert!(matches!(
222 encode_result(result),
223 Err(InvokeHtaError::ResultTooLarge {
224 maximum: MAX_INVOKE_HTA_RESULT_BYTES,
225 ..
226 })
227 ));
228 }
229}