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]

Reply via email to