davsclaus commented on code in PR #27678:
URL: https://github.com/apache/camel/pull/27678#discussion_r4240436340
##########
components/camel-wasm/src/main/java/org/apache/camel/component/wasm/WasmProducer.java:
##########
@@ -16,76 +16,335 @@
*/
package org.apache.camel.component.wasm;
+import java.io.ByteArrayOutputStream;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
-import org.apache.camel.Endpoint;
+import org.apache.camel.CamelExchangeException;
import org.apache.camel.Exchange;
-import org.apache.camel.spi.Resource;
-import org.apache.camel.spi.ResourceLoader;
+import org.apache.camel.ExchangeTimedOutException;
+import org.apache.camel.Message;
import org.apache.camel.support.DefaultProducer;
-import org.apache.camel.support.PluginHelper;
-import org.apache.camel.wasm.WasmFunction;
-import org.apache.camel.wasm.WasmSupport;
+import org.apache.camel.support.ResourceHelper;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.URISupport;
+import org.apache.camel.wasm.WasiCommand;
+import org.apache.camel.wasm.Wasm;
+import run.endive.runtime.TrapException;
import run.endive.wasm.Parser;
import run.endive.wasm.WasmModule;
+/**
+ * Runs a WASI Preview 1 command module once per exchange: the message body is
the standard input of the module and its
+ * standard output becomes the message body.
+ * <p/>
+ * The module is parsed once, when the producer is initialized, and every
exchange runs on a new instance of it, so
+ * exchanges never share guest memory or globals and need no lock. The
instance is dropped after its run, whatever the
+ * outcome.
+ */
public class WasmProducer extends DefaultProducer {
- private final String functionModule;
- private final String functionName;
+ private static final int MAX_STDERR_IN_MESSAGE = 256;
- private WasmModule module;
- private WasmFunction function;
+ private WasiCommand command;
+ private Map<String, String> environment = Collections.emptyMap();
+ private List<String> environmentHeaders = Collections.emptyList();
+ private volatile ExecutorService executor;
- public WasmProducer(Endpoint endpoint, String functionModule, String
functionName) throws Exception {
+ public WasmProducer(WasmEndpoint endpoint) {
super(endpoint);
+ }
- this.functionModule = functionModule;
- this.functionName = functionName;
+ @Override
+ public WasmEndpoint getEndpoint() {
+ return (WasmEndpoint) super.getEndpoint();
}
@Override
- public void doInit() throws Exception {
- final ResourceLoader rl =
PluginHelper.getResourceLoader(getEndpoint().getCamelContext());
- final Resource res = rl.resolveResource(this.functionModule);
+ protected void doInit() throws Exception {
+ super.doInit();
+
+ WasmEndpoint endpoint = getEndpoint();
+ WasmConfiguration conf = endpoint.getConfiguration();
+
+ Map<String, String> env = new LinkedHashMap<>();
+ if (conf.getEnvironment() != null) {
+ for (Map.Entry<String, Object> e :
conf.getEnvironment().entrySet()) {
+ String value = e.getValue() != null ? e.getValue().toString()
: "";
+ env.put(checkName(e.getKey()), checkNoNul("environment
variable " + e.getKey(), value));
+ }
+ }
+ List<String> headers = new ArrayList<>();
+ if (ObjectHelper.isNotEmpty(conf.getEnvironmentHeaders())) {
+ for (String header : conf.getEnvironmentHeaders().split(",")) {
+ String name = checkName(header.trim());
+ if (env.containsKey(name)) {
+ throw new IllegalArgumentException(
+ "Environment variable " + name + " is configured
both in environment and environmentHeaders");
+ }
+ headers.add(name);
+ }
+ }
+ List<String> arguments = arguments(endpoint.getModule(),
conf.getArgs());
Review Comment:
argv[0] is the raw module URI, so for a remote resource like
`http://user:pw@host/x.wasm?token=...` the module sees the credentials. The
error messages already go through `URISupport.sanitizeUri`; suggest the same
here (or just the file name), and adjust the `args` description/test
accordingly.
##########
components/camel-wasm/src/main/java/org/apache/camel/wasm/WasiCommand.java:
##########
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.wasm;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import run.endive.runtime.ImportValues;
+import run.endive.runtime.Instance;
+import run.endive.runtime.TrapException;
+import run.endive.wasi.WasiExitException;
+import run.endive.wasi.WasiOptions;
+import run.endive.wasi.WasiPreview1;
+import run.endive.wasm.WasmModule;
+import run.endive.wasm.types.Export;
+import run.endive.wasm.types.ExportSection;
+import run.endive.wasm.types.ExternalType;
+import run.endive.wasm.types.MemoryLimits;
+import run.endive.wasm.types.MemorySection;
+
+/**
+ * Runs a WASI Preview 1 command module (a program with a {@code _start}
function, such as a Rust program built for
+ * {@code wasm32-wasip1} or a Go program built with {@code GOOS=wasip1
GOARCH=wasm}) to completion.
+ * <p/>
+ * The module is parsed once by the caller and every {@link #run} creates a
new instance of it, so runs never share
+ * guest memory or globals and need no lock. A run gets exactly the standard
streams, arguments and environment
+ * variables it is given: no preopened directories (so no files) and no
sockets. Clock and random numbers are the
+ * host's.
+ */
+public final class WasiCommand {
+
+ private static final String START = "_start";
+ private static final String WASI_MODULE = "wasi_snapshot_preview1";
+
+ private final WasmModule module;
+ private final List<String> arguments;
+ private final MemoryLimits memoryLimits;
+
+ /**
+ * @param module the parsed module
+ * @param arguments the program's arguments, the program
name first
+ * @param maxMemoryPages the maximum number of 64 KiB pages the
module's memory may grow to, or 0 to keep
+ * the maximum the module declares
+ * @throws IllegalArgumentException if the module is not a WASI Preview 1
command module, or maxMemoryPages is lower
+ * than the initial memory the module
declares
+ */
+ public WasiCommand(WasmModule module, List<String> arguments, int
maxMemoryPages) {
+ this.module = Objects.requireNonNull(module);
+ this.arguments = List.copyOf(arguments);
+ validate(module);
+ this.memoryLimits = memoryLimits(module, maxMemoryPages);
+ }
+
+ /**
+ * Runs the program once, on a new instance, on the calling thread.
+ * <p/>
+ * A {@link RuntimeException} thrown by one of the given streams stops the
program and is rethrown.
+ *
+ * @param stdin the program's
standard input
+ * @param stdout receives the
program's standard output
+ * @param stderr receives the
program's standard error
+ * @param environment the program's
complete environment
+ * @return the exit code: 0
when {@code _start} returned, otherwise the
+ * value passed to
{@code proc_exit}
+ * @throws TrapException if the program
trapped (for example a Rust panic)
+ * @throws run.endive.runtime.WasmInterruptedException if the calling
thread was interrupted
+ */
+ public int run(InputStream stdin, OutputStream stdout, OutputStream
stderr, Map<String, String> environment) {
+ // built on the thread that runs the program: the default random
number generator is the thread's own.
+ // Endive reports the standard streams as terminals unless told
otherwise; they are not, and a program that
+ // sees a terminal may add terminal output, such as colours, to the
message body
+ WasiOptions.Builder options = WasiOptions.builder()
Review Comment:
`WasiOptions.builder()` is used without `withRandom(...)`. In Endive 1.1.0
the builder defaults `random` to `ThreadLocalRandom.current()` (checked in the
bytecode of `WasiOptions$Builder.<init>`), and `random_get` calls
`random.nextBytes(...)`.
WASI `random_get` is what Go `crypto/rand` and Rust `getrandom` use on
wasip1 (keys, nonces, UUIDs, HashMap seeds), so every module would get
predictable bytes. The comment above and the doc line *"Clock and random
numbers: the ones of the host"* suggest a secure source is expected.
Suggest a shared `SecureRandom`:
```java
private static final SecureRandom RANDOM = new SecureRandom();
...
WasiOptions.builder()
.withRandom(RANDOM)
```
and ideally a small test asserting it.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]