summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitattributes2
-rw-r--r--.github/workflows/build_tests.yaml41
-rw-r--r--.github/workflows/check-whitespace.yaml49
-rw-r--r--.gitignore7
-rw-r--r--.rustfmt.toml2
-rw-r--r--API-C.md962
-rw-r--r--CODE_OF_CONDUCT.md74
-rw-r--r--CONTRIBUTING.md83
-rw-r--r--HOWTO.md203
-rw-r--r--LICENSE177
-rw-r--r--Makefile73
-rw-r--r--README.md389
-rw-r--r--examples/.gitignore1
-rw-r--r--examples/c/Makefile40
-rw-r--r--examples/c/echo-request-raw.c187
-rw-r--r--examples/c/luw-echo-request.c98
-rw-r--r--examples/c/luw-upload-reflector.c101
-rw-r--r--examples/c/unit-wasm-raw.c46
-rw-r--r--examples/c/unit-wasm-raw.h87
-rw-r--r--examples/c/upload-reflector-raw.c223
-rw-r--r--examples/docker/README.md31
-rw-r--r--examples/docker/demo-wasm.Dockerfile20
-rw-r--r--examples/docker/unit-wasm.Dockerfile40
-rw-r--r--examples/docker/wasm-conf.json76
-rw-r--r--examples/rust/.gitignore3
-rw-r--r--examples/rust/Makefile17
-rw-r--r--examples/rust/echo-request/Cargo.toml12
-rw-r--r--examples/rust/echo-request/src/lib.rs200
-rw-r--r--examples/rust/upload-reflector/Cargo.toml12
-rw-r--r--examples/rust/upload-reflector/src/lib.rs131
-rw-r--r--shared.mk51
-rw-r--r--src/c/.gitignore1
-rw-r--r--src/c/Makefile21
-rw-r--r--src/c/include/unit/unit-wasm.h211
-rw-r--r--src/c/libunit-wasm.c381
-rw-r--r--src/rust/.gitignore4
-rw-r--r--src/rust/Cargo.toml15
-rw-r--r--src/rust/Makefile11
-rw-r--r--src/rust/README.md18
-rw-r--r--src/rust/src/ffi/mod.rs3
-rw-r--r--src/rust/src/lib.rs8
-rw-r--r--src/rust/unit-wasm-sys/Cargo.toml18
-rw-r--r--src/rust/unit-wasm-sys/README.md4
-rw-r--r--src/rust/unit-wasm-sys/build.rs52
-rw-r--r--src/rust/unit-wasm-sys/lib.rs19
l---------src/rust/unit-wasm-sys/libunit-wasm1
-rw-r--r--src/rust/unit-wasm-sys/macros.rs15
-rw-r--r--unit-wasm-conf.json87
48 files changed, 4307 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..45ec515
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+*.c diff=cpp
+*.h diff=cpp
diff --git a/.github/workflows/build_tests.yaml b/.github/workflows/build_tests.yaml
new file mode 100644
index 0000000..f77df2c
--- /dev/null
+++ b/.github/workflows/build_tests.yaml
@@ -0,0 +1,41 @@
+name: Builds
+
+on:
+ push:
+ branches: preview
+ paths:
+ - Makefile
+ - 'examples/**'
+ - 'src/**'
+ - '.github/workflows/build_tests.yaml'
+ pull_request:
+ branches: preview
+ paths:
+ - Makefile
+ - 'examples/**'
+ - 'src/**'
+ - '.github/workflows/build_tests.yaml'
+
+jobs:
+ # GitHub Currently only supports running directly on Ubuntu,
+ # for any other Linux we need to use a container.
+
+ fedora-rawhide:
+ runs-on: ubuntu-latest
+
+ container:
+ image: fedora:rawhide
+
+ steps:
+ - name: Install tools/deps
+ run: |
+ dnf -y install git wget clang llvm compiler-rt lld make bindgen-cli cargo rust rust-std-static-wasm32-unknown-unknown rust-std-static-wasm32-wasi
+ wget -O- https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/libclang_rt.builtins-wasm32-wasi-20.0.tar.gz | tar --strip-components=1 -xvzf - -C $(dirname $(rpm -ql compiler-rt | grep lib/libclang_rt.builtins-x86_64.a))
+ wget -O- https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sysroot-20.0.tar.gz | tar -xvzf - -C ${RUNNER_TEMP}
+
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: "0"
+
+ - name: make
+ run: make WASI_SYSROOT=${RUNNER_TEMP}/wasi-sysroot V=1 E=1 all
diff --git a/.github/workflows/check-whitespace.yaml b/.github/workflows/check-whitespace.yaml
new file mode 100644
index 0000000..3f99a99
--- /dev/null
+++ b/.github/workflows/check-whitespace.yaml
@@ -0,0 +1,49 @@
+name: check-whitespace
+
+# Get the repo with the commits(+1) in the series.
+# Process `git log --check` output to extract just the check errors.
+# Add a comment to the pull request with the check errors.
+
+on:
+ pull_request:
+ types: [ opened, synchronize ]
+
+jobs:
+ check-whitespace:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: git log --check
+ id: check_out
+ run: |
+ log=
+ commit=
+ while read dash etc
+ do
+ case "${dash}" in
+ "---")
+ commit="${etc}"
+ ;;
+ "")
+ ;;
+ *)
+ if test -n "${commit}"
+ then
+ log="${log}\n${commit}"
+ echo ""
+ echo "--- ${commit}"
+ fi
+ commit=
+ log="${log}\n${dash} ${etc}"
+ echo "${dash} ${etc}"
+ ;;
+ esac
+ done <<< $(git log --check --pretty=format:"--- %h %s" ${{github.event.pull_request.base.sha}}..)
+
+ if test -n "${log}"
+ then
+ exit 2
+ fi
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..50996ec
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+*.swp
+*~
+
+*.o
+*.gch
+
+tags
diff --git a/.rustfmt.toml b/.rustfmt.toml
new file mode 100644
index 0000000..12f259b
--- /dev/null
+++ b/.rustfmt.toml
@@ -0,0 +1,2 @@
+max_width = 80
+#binop_separator = "Back"
diff --git a/API-C.md b/API-C.md
new file mode 100644
index 0000000..fa04acb
--- /dev/null
+++ b/API-C.md
@@ -0,0 +1,962 @@
+# libunit-wasm C API
+
+C Library for creating WebAssembly modules for use with NGINX Unit.
+
+```C
+#include <unit/unit-wasm.h>
+```
+
+1. [libunit-wasm C API](#libunit-wasm-c-api)
+2. [Macros](#macros)
+ * [Version](#version)
+ * [Misc](#misc)
+3. [Types](#types)
+4. [Enums](#enums)
+5. [Structs](#structs)
+6. [Function Handlers](#function-handlers)
+ * [Optional](#optional)
+ - [luw_module_init_handler](#luw_module_init_handler)
+ - [luw_module_end_handler](#luw-_module_end_handler)
+ - [luw_request_init_handler](#luw_request_init_handler)
+ - [luw_request_end_handler](#luw_request_end_handler)
+ - [luw_response_end_handler](#luw_response_end_handler)
+ * [Required](#required)
+ - [luw_request_handler](#luw_request_handler)
+ - [luw_free_handler](#luw_free_handler)
+ - [luw_malloc_handler](#luw_malloc_handler)
+7. [Functions](#functions)
+ * [luw_init_ctx](#luw_init_ctx)
+ * [luw_set_req_buf](#luw_set_req_buf)
+ * [luw_get_http_path](#luw_get_http_path)
+ * [luw_get_http_method](#luw_get_http_method)
+ * [luw_get_http_version](#luw_get_http_version)
+ * [luw_get_http_query](#luw_get_http_query)
+ * [luw_get_http_remote](#luw_get_http_remote)
+ * [luw_get_http_local_addr](#luw_get_http_local_addr)
+ * [luw_get_http_local_port](#luw_get_http_local_port)
+ * [luw_get_http_server_name](#luw_get_http_server_name)
+ * [luw_get_http_content](#luw_get_http_content)
+ * [luw_get_http_content_len](#luw_get_http_content_len)
+ * [luw_get_http_content_sent](#luw_get_http_content_sent)
+ * [luw_http_is_tls](#luw_http_is_tls)
+ * [luw_http_hdr_iter](#luw_http_hdr_iter)
+ * [luw_http_hdr_get_value](#luw_http_hdr_get_value)
+ * [luw_get_response_data_size](#luw_get_response_data_size)
+ * [luw_mem_writep](#luw_mem_writep)
+ * [luw_mem_writep_data](#luw_mem_writep_data)
+ * [luw_req_buf_append](#luw_req_buf_append)
+ * [luw_mem_fill_buf_from_req](#luw_mem_fill_buf_from_req)
+ * [luw_mem_reset](#luw_mem_reset)
+ * [luw_http_send_response](#luw_http_send_response)
+ * [luw_http_init_headers](#luw_http_init_headers)
+ * [luw_http_add_header](#luw_http_add_header)
+ * [luw_http_send_headers](#luw_http_send_headers)
+ * [luw_http_response_end](#luw_http_response_end)
+ * [luw_mem_get_init_size](#luw_mem_get_init_size)
+ * [luw_foreach_http_hdr](#luw_foreach_http_hdr)
+8. [Misc. Functions](#misc-functions)
+ * [luw_malloc](#luw_malloc)
+ * [luw_free](#luw_free)
+
+## Macros
+
+### Version
+
+```C
+#define LUW_VERSION_MAJOR M
+#define LUW_VERSION_MINOR m
+#define LUW_VERSION_PATCH p
+```
+
+```C
+/* Version number in hex 0xMMmmpp00 */
+#define LUW_VERSION_NUMBER \
+ ( (LUW_VERSION_MAJOR << 24) | \
+ (LUW_VERSION_MINOR << 16) | \
+ (LUW_VERSION_PATCH << 8) )
+```
+
+### Misc
+
+```C
+#define __luw_export_name(name) __attribute__((export_name(name)))
+```
+
+```C
+#define __luw_unused __attribute__((unused))
+#define __luw_maybe_unused __luw_unused
+```
+
+```C
+#define luw_foreach_http_hdr(ctx, iter, name, value) \
+ for (iter = ctx.req->fields, \
+ name = (const char *)ctx.req + iter->name_off; \
+ (iter < (ctx.req->fields + ctx.req->nr_fields)) && \
+ (value = (const char *)ctx.req + iter->value_off); \
+ iter++, name = (const char *)ctx.req + iter->name_off)
+```
+
+## Types
+
+```C
+typedef uint64_t u64;
+typedef int64_t s64;
+typedef uint32_t u32;
+typedef int32_t s32;
+typedef uint16_t u16;
+typedef int16_t s16;
+typedef uint8_t u8;
+typedef int8_t s8;
+```
+
+## Enums
+
+```C
+typedef enum {
+ LUW_SRB_NONE = 0x00,
+ LUW_SRB_APPEND = 0x01,
+ LUW_SRB_ALLOC = 0x02,
+ LUW_SRB_FULL_SIZE = 0x04,
+} luw_srb_flags_t;
+#define LUW_SRB_FLAGS_ALL \
+ (LUW_SRB_NONE|LUW_SRB_APPEND|LUW_SRB_ALLOC|LUW_SRB_FULL_SIZE
+```
+
+## Structs
+
+```C
+struct luw_hdr_field {
+ u32 name_off;
+ u32 name_len;
+ u32 value_off;
+ u32 value_len;
+};
+```
+
+```C
+struct luw_req {
+ u32 method_off;
+ u32 method_len;
+ u32 version_off;
+ u32 version_len;
+ u32 path_off;
+ u32 path_len;
+ u32 query_off;
+ u32 query_len;
+ u32 remote_off;
+ u32 remote_len;
+ u32 local_addr_off;
+ u32 local_addr_len;
+ u32 local_port_off;
+ u32 local_port_len;
+ u32 server_name_off;
+ u32 server_name_len;
+
+ u32 content_off;
+ u32 content_len;
+ u32 content_sent;
+ u32 total_content_sent;
+
+ u32 request_size;
+
+ u32 nr_fields;
+
+ u32 tls;
+
+ struct luw_hdr_field fields[];
+};
+```
+
+```C
+struct luw_resp {
+ u32 size;
+
+ u8 data[];
+};
+```
+
+```C
+struct luw_resp_hdr {
+ u32 nr_fields;
+
+ struct luw_hdr_field fields[];
+};
+```
+
+```C
+typedef struct {
+ /* pointer to the shared memory */
+ u8 *addr;
+
+ /* points to the end of ctx->resp->data */
+ u8 *mem;
+
+ /* struct luw_req representation of the shared memory */
+ struct luw_req *req;
+
+ /* struct luw_resp representation of the shared memory */
+ struct luw_resp *resp;
+
+ /* struct luw_resp_hdr representation of the shared memory */
+ struct luw_resp_hdr *resp_hdr;
+
+ /* offset to where the struct resp starts in the shared memory */
+ size_t resp_offset;
+
+ /* points to the external buffer used for a copy of the request */
+ u8 *req_buf;
+
+ /* points to the end of the fields array in struct luw_resp_hdr */
+ u8 *hdrp;
+
+ /* points to the end of ctx->req_buf */
+ u8 *reqp;
+} luw_ctx_t;
+```
+
+```C
+typedef struct luw_hdr_field luw_http_hdr_iter_t;
+```
+
+## Function Handlers
+
+These functions are exported from the WebAssembly module and are called from
+the WebAssembly runtime (the Unit WebAssembly language module in this case).
+
+There are two types of handlers; required & optional.
+
+luw_request_handler(), luw_malloc_handler() & luw_free_handler() are required
+with the rest being optional.
+
+libunit-wasm includes exports for these handlers and some default
+implementations.
+
+These functions are defined as _weak_ symbols and so if a developer writes
+their own function of the same name, that will take precedence.
+
+However, developers are under no obligation to use these and can create their
+own with any (valid) names they like.
+
+Whatever names developers choose, they are specified in the Unit config.
+
+## Required
+
+#### luw_request_handler
+
+```C
+__attribute__((export_name("luw_request_handler"), __weak__))
+int luw_request_handler(u8 *addr);
+```
+
+This is called by Unit during a request. It may be called multiple times for
+a single HTTP request if there is more request data than the available memory
+for host <--> module communications.
+
+You will need to provide your own implementation of this function.
+
+It receives the base address of the shared memory. Essentially what is
+returned by luw_malloc_handler().
+
+This memory will contain a *struct luw_req*.
+
+It returns an int, this is currently ignored but will likely be used to
+indicate a HTTP status code.
+
+#### luw_malloc_handler
+
+```C
+__attribute__((export_name("luw_malloc_handler"), __weak__))
+u32 luw_malloc_handler(size_t size);
+```
+
+This is called by Unit when it loads the WebAssembly language module. This
+provides the shared memory used for host <--> module communications.
+
+It receives the desired size of the memory, which is currently
+NXT_WASM_MEM_SIZE + NXT_WASM_PAGE_SIZE.
+
+However calls to luw_mem_get_init_size() will return just NXT_WASM_MEM_SIZE
+(which is currently 32MiB). The extra NXT_WASM_PAGE_SIZE is to cater for
+structure sizes in the response so developers can generally assume they have
+the full NXT_WASM_MEM_SIZE for their data.
+
+A default implementation of this function is provided ready for use that
+calls malloc(3).
+
+#### luw_free_handler
+
+```C
+__attribute__((export_name("luw_free_handler"), __weak__))
+void luw_free_handler(u32 addr);
+```
+
+This is called by Unit when it shuts down the WebAssembly language module and
+free's the memory previously allocated by luw_malloc_handler().
+
+It receives the address of the memory to free.
+
+An implementation of this function is provided ready for use that calls
+free(3), in which case it receives the address that was previously returned
+by luw_malloc_handler().
+
+### Optional
+
+#### luw_module_init_handler
+
+```C
+__attribute__((export_name("luw_module_init_handler"), __weak__))
+void luw_module_init_handler(void);
+```
+
+This is called by Unit when it loads the WebAssembly language module.
+
+A default dummy function is provided. If this handler is not required, there
+is no need to specify it in the Unit config.
+
+#### luw_module_end_handler
+
+```C
+__attribute__((export_name("luw_module_end_handler"), __weak__))
+void luw_module_end_handler(void);
+```
+
+This is called by Unit when it shuts down the WebAssembly language module.
+
+A default dummy function is provided. If this handler is not required, there
+is no need to specify it in the Unit config.
+
+#### luw_request_init_handler
+
+```C
+__attribute__((export_name("luw_request_init_handler"), __weak__))
+void luw_request_init_handler(void);
+```
+
+This is called by Unit at the start of nxt_wasm_request_handler(), i.e at the
+start of a new request.
+
+A default dummy function is provided. If this handler is not required, there
+is no need to specify it in the Unit config.
+
+#### luw_request_end_handler
+
+```C
+__attribute__((export_name("luw_request_end_handler"), __weak__))
+void luw_request_end_handler(void);
+```
+
+This is called by Unit at the end of nxt_wasm_request_handler(), i.e at the
+end of a request.
+
+A default dummy function is provided. If this handler is not required, there
+is no need to specify it in the Unit config.
+
+#### luw_response_end_handler
+
+```C
+__attribute__((export_name("luw_response_end_handler"), __weak__))
+void luw_response_end_handler(void);
+```
+
+This is called by Unit after luw_http_response_end() has been called.
+
+A default dummy function is provided. If this handler is not required, there
+is no need to specify it in the Unit config.
+
+## Functions
+
+### luw_init_ctx
+
+```C
+void luw_init_ctx(luw_ctx_t *ctx, u8 *addr, size_t offset);
+```
+
+This function sets up a *luw_ctx_t* context structure, this contains stuff
+required all throughout the API. It's a typedef for opaqueness and you should
+not in general be concerned with its contents.
+
+It take a pointer to a stack allocated luw_ctx_t, this will be zeroed and
+have various members initialised.
+
+**addr** is a pointer to the shared memory as passed into luw_request_handler().
+
+**offset** is where in the shared memory it should start writing the response.
+
+#### A quick word about memory
+
+The way the Unit WebAssembly language module (the host/runtime) and the
+WebAssembly module you want to write (the guest) communicate is via a chunk
+of shared memory.
+
+This shared memory is simply the modules (guest) address space from which we
+can allocate a chunk. How this memory is laid out varies on how the module
+is built.
+
+With clang/linker flags of -Wl,--stack-first -Wl,-z,stack-size=$((8*1024*1024))
+we get a memory layout something like
+
+```
+ |----------------------------------------------------------------------|
+ | | | |
+ | <-- Stack | Global Data | Heap --> |
+ | | | |
+ |----------------------------------------------------------------------|
+ 0 0x800000 0x100000000
+
+ WebAssembly Module Linear Memory / Process Memory Layout
+```
+
+(The above is assuming _--target=wasm32-wasi_, i.e 32bit)
+
+A chunk of memory from the heap is allocated at Unit WebAssembly language
+module startup.
+
+We currently use this same chunk of memory for both requests and responses.
+This means that depending on what you're doing, you'll want to take a copy
+of the request (and remember luw_request_handler() may be called multiple
+times for a single http request).
+
+That will be covered in more detail by the next function, luw_set_req_buf().
+
+Now back to _offset_, it may be convenient to put the response headers at the
+beginning of this memory and then put the response after it, rather than
+doing the headers and then doing the response as separate steps, if the
+headers depends on some aspect of the response, its size for example and
+Content-Length.
+
+Example
+
+```C
+luw_ctx_t ctx;
+/* ... */
+luw_init_ctx(&ctx, addr, 4096 /* Response offset */);
+```
+
+### luw_set_req_buf
+
+```C
+int luw_set_req_buf(luw_ctx_t *ctx, u8 **buf, unsigned long flags);
+```
+
+This function is used to take a copy of the request buffer (as discussed
+above).
+
+This takes a previously initialised (with luw_init_ctx()) luw_ctx_t.
+
+**buf** is a buffer where the request data will written.
+
+**flags** can be some combination (OR'd) of the following
+
+**LUW_SRB_NONE**
+
+No specific action to be performed. It will simply copy the request data
+into the specified buffer.
+
+**LUW_SRB_APPEND**
+
+Sets up append mode whereby multiple successive requests will be appended
+to the specified buffer.
+
+The first request will have all its metadata copied. Subsequent requests
+will _only_ have the actual body data appended.
+
+**LUW_SRB_ALLOC**
+
+Allocate memory for the specified buffer.
+
+**LUW_SRB_FULL_SIZE**
+
+Used in conjunction with *LUW_SRB_ALLOC*. By default only
+*ctx->req->request_size* is allocated. If this flag is present it says to
+allocate memory for the _entire_ request that will eventually be sent.
+
+Example
+
+```C
+static u8 *request_buf;
+*/ ... */
+int luw_request_handler(u8 *addr)
+{
+ if (!request_buf) {
+ luw_init_ctx(&ctx, addr, 0);
+ /*
+ * Take a copy of the request and use that, we do this
+ * in APPEND mode so we can build up request_buf from
+ * multiple requests.
+ *
+ * Just allocate memory for the total amount of data we
+ * expect to get, this includes the request structure
+ * itself as well as any body content.
+ */
+ luw_set_req_buf(&ctx, &request_buf,
+ LUW_SRB_APPEND|LUW_SRB_ALLOC|LUW_SRB_FULL_SIZE);
+ } else {
+ luw_req_buf_append(&ctx, addr);
+ }
+
+ /* operate on the request (ctx) */
+
+ return 0;
+}
+```
+
+That example is taken from the [luw-upload-reflector.c](https://github.com/nginx/unit-wasm/blob/master/examples/c/luw-upload-reflector.c) demo module. For a
+simpler example see [luw-echo-request.c](https://github.com/nginx/unit-wasm/blob/master/examples/c/luw-echo-request.c)
+
+### luw_get_http_path
+
+```C
+const char *luw_get_http_path(const luw_ctx_t *ctx);
+```
+
+This function returns a pointer to the HTTP request path.
+
+E.g
+
+Given a request of
+```
+http://localhost:8080/echo/?q=a
+```
+this function will return
+```
+/echo/?q=a
+```
+
+### luw_get_http_method
+
+```C
+const char *luw_get_http_method(const luw_ctx_t *ctx);
+```
+
+This function returns a pointer to the HTTP method.
+
+E.g
+
+```
+GET
+```
+
+### luw_get_http_version
+
+```C
+const char *luw_get_http_version(const luw_ctx_t *ctx);
+```
+
+This function returns a pointer to the HTTP version.
+
+E.g
+
+```
+1.1
+```
+
+### luw_get_http_query
+
+```C
+const char *luw_get_http_query(const luw_ctx_t *ctx);
+```
+
+This function returns a pointer to the query string (empty string for no query
+string).
+
+E.g
+
+Given a request of
+```
+http://localhost:8080/echo/?q=a
+```
+this function will return
+```
+q=a
+```
+
+### luw_get_http_remote
+
+```C
+const char *luw_get_http_remote(const luw_ctx_t *ctx);
+```
+
+This function returns a pointer to the remote/client/peer address.
+
+E.g
+
+```
+2001:db8::f00
+```
+
+### luw_get_http_local_addr
+
+```C
+const char *luw_get_http_local_addr(const luw_ctx_t *ctx);
+```
+
+This function returns a pointer to the local/server address.
+
+E.g
+
+```
+2001:db8::1
+```
+
+### luw_get_http_local_port
+
+```C
+const char *luw_get_http_local_port(const luw_ctx_t *ctx);
+```
+
+This function returns a pointer to the local/server port.
+
+E.g
+
+```
+443
+```
+
+### luw_get_http_server_name
+
+```C
+const char *luw_get_http_server_name(const luw_ctx_t *ctx);
+```
+
+This function returns a pointer to the local/server name.
+
+E.g
+
+```
+www.example.com
+```
+
+### luw_get_http_content
+
+```C
+const u8 *luw_get_http_content(const luw_ctx_t *ctx);
+```
+
+This function returns a pointer to the start of the request body.
+
+### luw_get_http_content_len
+
+```C
+size_t luw_get_http_content_len(const luw_ctx_t *ctx);
+```
+
+This function returns the size of the overall content. I.e Content-Length.
+
+
+### luw_get_http_content_sent
+
+```C
+size_t luw_get_http_content_sent(const luw_ctx_t *ctx);
+```
+
+This function returns the length of the content that was sent to the
+WebAssembly module in _this_ request. Remember, a single HTTP request may be
+split over several calls to luw_request_handler().
+
+### luw_http_is_tls
+
+```C
+bool luw_http_is_tls(const luw_ctx_t *ctx);
+```
+
+This function returns _true_ if the connection to Unit was made over TLS.
+
+### luw_http_hdr_iter
+
+```C
+void luw_http_hdr_iter(luw_ctx_t *ctx,
+ bool (*luw_http_hdr_iter_func)(luw_ctx_t *ctx,
+ const char *name,
+ const char *value,
+ void *data),
+ void *user_data)
+```
+
+This function allows to iterate over the HTTP headers. For each header it
+will call the given luw_http_hdr_iter_func() function whose prototype is
+
+```C
+bool luw_http_hdr_iter_func(luw_ctx_t *ctx,
+ const char *name, const char *value, void *data);
+```
+
+You may call this function whatever you like. For each header it will be
+passed the *luw_ctx_t*, the header name, its value and a user specified
+pointer if any, can be NULL.
+
+Returning _true_ from this function will cause the iteration process to
+continue, returning _false_ will terminate it.
+
+Example
+
+```C
+static bool hdr_iter_func(luw_ctx_t *ctx, const char *name, const char *value,
+ void *user_data __luw_unused)
+{
+ /* Do something with name & value */
+
+ /* Continue iteration or return false to stop */
+ return true;
+}
+
+/* ... *
+
+luw_http_hdr_iter(&ctx, hdr_iter_func, NULL);
+```
+
+### luw_http_hdr_get_value
+
+```C
+const char *luw_http_hdr_get_value(luw_ctx_t *ctx, const char *hdr);
+```
+
+Given a HTTP header _hdr_ this function will look it up in the request and
+return its value if found, otherwise _NULL_.
+
+The lookup is done case insensitively.
+
+### luw_get_response_data_size
+
+```C
+size_t luw_get_response_data_size(const luw_ctx_t *ctx);
+```
+
+This function returns the size of the response data written to memory.
+
+### luw_mem_writep
+
+```C
+__attribute__((__format__(printf, 2, 3)))
+int luw_mem_writep(luw_ctx_t *ctx, const char *fmt, ...);
+```
+
+This function is a cross between vasprintf(3) and mempcpy(3).
+
+It takes a format argument and zero or more arguments that will be
+substituted into the format string.
+
+It then appends this formatted string to the memory. Note this string will
+_not_ be nul terminated. Unit does not expect this response data to be nul
+terminated and we track the size of the response and return that to Unit.
+
+This function returns -1 on error or the length of the string written.
+
+### luw_mem_writep_data
+
+```C
+size_t luw_mem_writep_data(luw_ctx_t *ctx, const u8 *src, size_t size);
+```
+
+This function just appends _size_ bytes from _src_ to the response.
+
+It returns the new size of the response.
+
+### luw_req_buf_append
+
+```C
+void luw_req_buf_append(luw_ctx_t *ctx, const u8 *src);
+```
+
+This function appends the request data contained in _src_ to the previously
+setup *request_buffer* with luw_set_req_buf().
+
+This function would be used after an initial request to append the data from
+subsequent requests to the request_buffer.
+
+Example
+
+```C
+int luw_request_handler(u8 *addr)
+{
+ if (!request_buf) {
+ luw_init_ctx(&ctx, addr, 0);
+ /*
+ * Take a copy of the request and use that, we do this
+ * in APPEND mode so we can build up request_buf from
+ * multiple requests.
+ *
+ * Just allocate memory for the total amount of data we
+ * expect to get, this includes the request structure
+ * itself as well as any body content.
+ */
+ luw_set_req_buf(&ctx, &request_buf,
+ LUW_SRB_APPEND|LUW_SRB_ALLOC|LUW_SRB_FULL_SIZE);
+ } else {
+ luw_req_buf_append(&ctx, addr);
+ }
+
+ /* Do something with the request (ctx) */
+
+ return 0;
+}
+```
+
+### luw_mem_fill_buf_from_req
+
+```C
+size_t luw_mem_fill_buf_from_req(luw_ctx_t *ctx, size_t from);
+```
+
+This is a convenience function to fill the response buffer with data from
+the request buffer.
+
+_from_ is basically the offset in the request_buffer where to start copying
+data from.
+
+Example
+
+```C
+/* ... */
+write_bytes = luw_mem_fill_buf_from_req(ctx, total_response_sent);
+total_response_sent += write_bytes;
+/* ... */
+```
+
+This is taken from the [luw-upload-reflector.c](https://github.com/nginx/unit-wasm/blob/master/examples/c/luw-upload-reflector.c) demo module.
+
+In this case we build up a request_buffer on each call of
+luw_request_handler(), so total_response_sent grows each time by how much data
+was sent in _that_ request.
+
+Here are are sending data back to the client after each time we receive it to
+demonstrate the interleaving of requests and responses from the WebAssembly
+module during a single http request.
+
+This function returns the number of bytes written to the response buffer.
+
+### luw_mem_reset
+
+```C
+void luw_mem_reset(luw_ctx_t *ctx);
+```
+
+This function resets the response buffer size and the number of response
+headers back to 0.
+
+### luw_http_send_response
+
+```C
+void luw_http_send_response(const luw_ctx_t *ctx);
+```
+
+This function calls into Unit to send the response buffer back.
+
+### luw_http_init_headers
+
+```C
+void luw_http_init_headers(luw_ctx_t *ctx, size_t nr, size_t offset);
+```
+
+This function is used in the preparation of sending back response headers.
+
+_nr_ is the number of headers we are sending.
+
+_offset_ is the offset into the response buffer where we are placing these
+headers. This will usually be 0.
+
+Example
+
+```C
+luw_http_init_headers(ctx, 2, 0);
+```
+
+### luw_http_add_header
+
+```C
+void luw_http_add_header(luw_ctx_t *ctx, u16 idx, const char *name,
+ const char *value);
+```
+
+This function is used to add a header to the response.
+
+_idx_ is the index (starting at 0) of the header we are adding.
+
+_name_ is the name of the header.
+
+_value_ is the value of the header.
+
+Example
+
+```C
+char clen[32];
+/* ... */
+snprintf(clen, sizeof(clen), "%lu", luw_get_response_data_size(&ctx));
+luw_http_add_header(&ctx, 0, "Content-Type", "text/plain");
+luw_http_add_header(&ctx, 1, "Content-Length", clen);
+```
+
+### luw_http_send_headers
+
+```C
+void luw_http_send_headers(const luw_ctx_t *ctx);
+```
+
+This function calls into Unit and triggers the sending of the response
+headers.
+
+### luw_http_response_end
+
+```C
+void luw_http_response_end(void);
+```
+
+This function calls into Unit and tells it this is the end of the response
+which will trigger Unit to send it to the client.
+
+### luw_mem_get_init_size
+
+```C
+u32 luw_mem_get_init_size(void);
+```
+
+This function calls into Unit to get the size of the shared memory. This is
+the amount of memory you should assume you have for creating responses.
+Remember you can create multiple responses before calling
+luw_http_response_end().
+
+### luw_foreach_http_hdr
+
+```C
+void luw_foreach_http_hdr(luw_ctx_t ctx, luw_http_hdr_iter_t *iter,
+ const char *name, const char *value)
+```
+
+Defined as a macro, this is used to iterate over the HTTP header fields.
+
+It takes a _luw_ctx_t *_ and a _luw_http_hdr_iter_t *_ and returns pointers
+to the field name and value.
+
+Example
+
+```C
+luw_ctx_t ctx;
+luw_http_hdr_iter_t *iter;
+const char *name;
+const char *value;
+/* ... */
+luw_foreach_http_hdr(ctx, iter, name, value) {
+ printf("Field name : %s, field value : %s\n", name, value);
+ /* do something else with name & value */
+}
+```
+
+## Misc. Functions
+
+The following functions are convenience wrappers for the Rust bindings and
+should **not** be used directly.
+
+### luw_malloc
+
+```C
+void *luw_malloc(size_t size);
+```
+
+Straight wrapper for malloc(3).
+
+### luw_free
+
+```C
+void luw_free(void *ptr);
+```
+
+Straight wrapper for free(3).
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..aea287f
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,74 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+- The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+- Trolling, insulting/derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the moderation team at nginx-oss-community@f5.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4,
+available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
+
+For answers to common questions about this code of conduct, see
+<https://www.contributor-covenant.org/faq>
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..19ead6e
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,83 @@
+# Contributing Guidelines
+
+The following is a set of guidelines for contributing to unit-wasm. We do
+appreciate that you are considering contributing!
+
+## Table Of Contents
+
+- [Getting Started](#getting-started)
+- [Ask a Question](#ask-a-question)
+- [Contributing](#contributing)
+- [Git Style Guide](#git-style-guide)
+
+
+## Getting Started
+
+Check out the [README](README.md).
+
+
+## Ask a Question
+
+Please open an [issue](https://github.com/nginx/unit-wasm/issues/new) on
+GitHub with the label `question`. You can also ask a question on
+[Slack](https://nginxcommunity.slack.com) or the NGINX Unit mailing list,
+unit@nginx.org (subscribe
+[here](https://mailman.nginx.org/mailman3/lists/unit.nginx.org/)).
+
+
+## Contributing
+
+### Report a Bug
+
+Ensure the bug was not already reported by searching on GitHub under
+[Issues](https://github.com/nginx/unit-wasm/issues).
+
+If the bug is a potential security vulnerability, please report using our
+[security policy](https://unit.nginx.org/troubleshooting/#getting-support).
+
+To report a non-security bug, open an
+[issue](https://github.com/nginx/unit-wasm/issues/new) on GitHub with the
+label `bug`. Be sure to include a title and clear description, as much
+relevant information as possible, and a code sample or an executable test
+case showing the expected behavior that doesn't occur.
+
+
+### Suggest an Enhancement
+
+To suggest an enhancement, open an
+[issue](https://github.com/nginx/unit/issues/new) on GitHub with the label
+`enhancement`. Please do this before implementing a new feature to discuss
+the feature first.
+
+
+### Open a Pull Request
+
+Clone the repo, create a branch, and submit a PR when your changes are tested
+and ready for review. Again, if you'd like to implement a new feature, please
+consider creating a feature request issue first to start a discussion about
+the feature.
+
+
+## Git Style Guide
+
+- Split your work into multiple commits is necessary. Each commit should make
+ one logical change. I.e don't mix code re-formatting with a fix in the same
+ commit.
+
+- Subject lines should be short (around 50 characters, not a hard rule) and
+ concisely describe the change.
+
+- The commit message body should be limited to 72 character lines.
+
+- You can use subject line prefixes for commits that affect a specific
+ portion of the code; examples include "libunit-wasm:" and "rust-bindings:".
+
+- Reference issues and PRs at the end of the commit messages, e.g if the
+ commit remedies a GitHub issue add a tag like
+
+ Closes: <https://github.com/nginx/unit-wasm/issues/NNN>
+
+ If the commit fixes an issue introduced in a previous commit use the "Fixes"
+ tag to reference it, e.g
+
+ Fixes: abbrev commit id ("Commit subject line")
diff --git a/HOWTO.md b/HOWTO.md
new file mode 100644
index 0000000..c2d2ae7
--- /dev/null
+++ b/HOWTO.md
@@ -0,0 +1,203 @@
+Trying it out
+=============
+
+For a quick and simple 'hello world' experience, you can use docker with
+
+```shell
+$ make docker
+```
+
+which will create two images:
+
+ 1. `unit:wasm` (based on the Docker Official image, with Wasm Module)
+ 2. `unit:demo-wasm` (based on the Wasm image, with demo application)
+
+Manual build instructions below.
+
+## Prerequisites and Assumptions
+
+You will need:
+ * Modern Linux platform (might work on others, not yet tested).
+ * Ability to build Unit from source.
+ If you haven't done this before, please first run through the
+[Building From Source how-to guide](https://unit.nginx.org/howto/source/).
+ * Additional build tools (required for the demo Wasm Module)
+ - clang
+ - llvm
+ - lld
+
+## Building the Wasm Language Module
+
+0. Do a test build of Unit from source ([see docs](https://unit.nginx.org/howto/source/)) with this PR/patch applied. The following steps assume you're
+starting in the `unit` directory and used `./configure --prefix=$PWD/build`.
+
+2. Download and extract the Wasmtime C API (newer versions may or may not
+work). Notice that we use `$(arch)` to substitute-in the appropriate CPU
+architecture. This works for **x86_64** and **aarch64** (ARM) platforms.
+```
+wget -O- https://github.com/bytecodealliance/wasmtime/releases/download/v11.0.0/wasmtime-v11.0.0-$(arch)-linux-c-api.tar.xz | tar Jxfv -
+```
+
+3. Configure the Wasm Language Module for Unit
+```
+./configure wasm --include-path=$PWD/wasmtime-v11.0.0-$(arch)-linux-c-api/include \
+ --lib-path=$PWD/wasmtime-v11.0.0-$(arch)-linux-c-api/lib --rpath
+```
+
+4. Build the Wasm Language Module
+```
+make
+```
+
+5. Test that **unitd** Can Load the Language Module
+
+Run `unitd` in the foreground (attached to the console) to check that Unit
+can discover and load the `wasm` Language Module at startup. You should see
+console output similar to this:
+```
+$ $PWD/build/sbin/unitd --no-daemon --log /dev/stderr
+2023/06/15 11:29:31 [info] 1#1 unit 1.31.0 started
+2023/06/15 11:29:31 [info] 43#43 discovery started
+2023/06/15 11:29:31 [notice] 43#43 module: wasm 0.1 "/path/to/modules/wasm.unit.so"
+```
+
+## Building the demo application
+
+From a suitable directory...
+
+Clone the [unit-wasm](https://github.com/nginx/unit-wasm) repository
+
+```shell
+$ git clone https://github.com/nginx/unit-wasm.git
+```
+
+Download and extract the wasi-sysroot from the [WASI SDK](https://github.com/WebAssembly/wasi-sdk)
+
+```shell
+wget -O- https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sysroot-20.0.tar.gz | tar zxfv -
+```
+
+Next Compile the C demo Wasm Modules to `.wasm` files. This requires at least
+the following; make and clang, llvm, compiler-rt, and lld from LLVM 8.0+
+
+```shell
+$ cd unit-wasm
+$ make WASI_SYSROOT=../wasi-sysroot examples
+```
+
+If the above fails like
+
+```
+wasm-ld: error: cannot open /usr/lib/llvm-11/lib/clang/11.0.1/lib/wasi/libclang_rt.builtins-wasm32.a: No such file or directory
+clang: error: linker command failed with exit code 1 (use -v to see invocation)
+```
+Then you need to download the wasm32 clang runtime and copy it into the
+location mentioned in the error message.
+
+E.g
+
+In the above case we would untar
+*libclang_rt.builtins-wasm32-wasi-20.0.tar.gz* into
+*/usr/lib/llvm-11/lib/clang/11.0.1/*
+
+On Fedora this would be more like */usr/lib64/clang/16/*
+
+Adjust the tar '-C ...' option accordingly below...
+
+```shell
+wget -O- https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/libclang_rt.builtins-wasm32-wasi-20.0.tar.gz | sudo tar -xvzf - -C /usr/lib/llvm-11/lib/clang/11.0.1
+```
+
+Then try again...
+
+If everything built OK then you should have the following two WASM modules
+
+```
+examples/c/luw-echo-request.wasm
+examples/c/luw-upload-reflector.wasm
+```
+
+## Configure Unit to run the demo application
+
+```json
+ {
+ "listeners": {
+ "[::1]:8080": {
+ "pass": "routes"
+ }
+ },
+
+ "settings": {
+ "http": {
+ "max_body_size": 1073741824
+ }
+ },
+
+ "routes": [
+ {
+ "match": {
+ "uri": "/echo*"
+ },
+ "action": {
+ "pass": "applications/luw-echo-request"
+ }
+ },
+ {
+ "match": {
+ "uri": "/upload*"
+ },
+ "action": {
+ "pass": "applications/luw-upload-reflector"
+ }
+ }
+ ],
+
+ "applications": {
+ "luw-echo-request": {
+ "type": "wasm",
+ "module": "/path/to/unit-wasm/examples/c/luw-echo-request.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "module_init_handler": "luw_module_init_handler",
+ "module_end_handler": "luw_module_end_handler",
+ "access": {
+ "filesystem": [
+ "/tmp",
+ "/foo/bar"
+ ]
+ }
+ },
+ "luw-upload-reflector": {
+ "type": "wasm",
+ "module": "/path/to/unit-wasm/examples/c/luw-upload-reflector.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "request_end_handler": "luw_request_end_handler",
+ "response_end_handler": "luw_response_end_handler"
+ }
+ }
+}
+
+```
+
+Apply the above configuration to the **/config** URI of Unit's Control API.
+With the JSON in a file, you can use the CLI to apply it.
+```
+cat conf.json | tools/unitc /config
+```
+
+The following messages should then appear in the Unit log file (or console if
+running with `--no-daemon`).
+```
+2023/07/26 13:28:14 [info] 182585#182585 "luw-echo-request" prototype started
+2023/07/26 13:28:14 [info] 182590#182590 "luw-echo-request" application started
+2023/07/26 13:28:14 [info] 182591#182591 "luw-upload-reflector" prototype started
+2023/07/26 13:28:14 [info] 182596#182596 "luw-upload-reflector" application started
+```
+
+Now make a request to the demo application.
+```
+curl http://localhost:8080/echo
+```
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f433b1a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1a56cb0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,73 @@
+MAKE_OPTS = --no-print-directory
+
+.PHONY: libunit-wasm
+libunit-wasm:
+ @echo "Building: libunit-wasm"
+ @$(MAKE) $(MAKE_OPTS) -C src/c
+
+.PHONY: examples
+examples: libunit-wasm
+ @echo "Building: examples"
+ @$(MAKE) $(MAKE_OPTS) -C examples/c examples-luw
+
+.PHONY: examples-raw
+examples-raw: libunit-wasm
+ @echo "Building: raw examples"
+ @$(MAKE) $(MAKE_OPTS) -C examples/c examples-raw
+
+.PHONY: rust
+rust: libunit-wasm
+ @echo "Building: libunit-wasm-rust"
+ @$(MAKE) $(MAKE_OPTS) -C src/rust
+
+.PHONY: examples-rust
+examples-rust: rust
+ @echo "Building: rust examples"
+ @$(MAKE) $(MAKE_OPTS) -C examples/rust
+
+.PHONY: all
+all: libunit-wasm examples examples-raw rust examples-rust
+
+.PHONY: docker
+docker:
+ docker build -t unit:wasm -f examples/docker/unit-wasm.Dockerfile .
+ docker build -t unit:demo-wasm -f examples/docker/demo-wasm.Dockerfile .
+
+.PHONY: clean
+clean:
+ @echo "Cleaning: libunit-wasm"
+ @$(MAKE) $(MAKE_OPTS) -C src/c clean
+ @echo "Cleaning: rust"
+ @$(MAKE) $(MAKE_OPTS) -C src/rust clean
+ @echo "Cleaning: examples"
+ @$(MAKE) $(MAKE_OPTS) -C examples/c clean
+ @echo "Cleaning: rust examples"
+ @$(MAKE) $(MAKE_OPTS) -C examples/rust clean
+
+.PHONY: tags
+tags:
+ @echo "Generating ctags..."
+ @ctags -R src/ examples/
+
+.PHONY: help
+help:
+ @echo "Available Targets:"
+ @echo " default / "
+ @echo " libunit-wasm - Builds libunit-wasm C library"
+ @echo " examples - Builds the above as well as C examples"
+ @echo " examples-raw - Builds raw (non libunit-wasm) C examples"
+ @echo " rust - Builds the libunit-wasm rust crate"
+ @echo " examples-rust _ Builds the above and rust examples"
+ @echo " all - Builds all the above"
+ @echo " docker - Builds demo docker images"
+ @echo " clean - Removes auto generated artifacts"
+ @echo " tags - Generate ctags"
+ @echo
+ @echo "Variables:"
+ @echo " make CC= - Specify compiler to use"
+ @echo " Defaults to clang"
+ @echo " make WASI_SYSROOT= - Specify the path to the WASI sysroot"
+ @echo " Defaults to /usr/wasm32-wasi"
+ @echo " make V=1 - Enables verbose output"
+ @echo " make D=1 - Enables debug builds (-O0)"
+ @echo " make E=1 - Enables Werror"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6292a0d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,389 @@
+# C & Rust Library & Examples for Building WebAssembly Modules for NGINX Unit
+
+This provides a C library (lbunit-wasm) and a Rust crate based on that library
+to aid in the creation of WebAssembly modules in C and Rust.
+
+It also has some demo WebAssembly modules written in C and Rust.
+
+1. [C & Rust Library & Examples for Building WebAssembly Modules for NGINX Unit](#c---rust-library---examples-for-building-webassembly-modules-for-nginx-unit)
+2. [Repository Layout](#repository-layout)
+3. [Quickstart in Developing Rust WebAssembly Modules for Unit](#quickstart-in-developing-rust-webassembly-modules-for-unit)
+4. [Getting Started](#getting-started)
+ 1. [Requirements](#requirements)
+ * [wasi-sysroot](#wasi-sysroot)
+ * [libclang_rt.builtins-wasm32-wasi](#libclang_rtbuiltins-wasm32-wasi)
+ 2. [Building libunit-wasm and C Examples](#building-libunit-wasm-and-c-examples)
+ 3. [Building the Rust libunit-wasm Crate and Examples](#building-the-rust-libunit-wasm-crate-and-examples)
+ 4. [Using With Unit](#using-with-unit)
+5. [Consuming the C Library](#consuming-the-c-library)
+6. [License](#license)
+
+## Repository Layout
+
+**src/c** contains the main libunit-wasm library.
+
+**src/rust** contains the rust version of the above.
+
+**examples/c** contains some demo WebAssembly modules that show both the raw
+interface to Unit (\*-raw.c) and also the use of libunit-wasm (luw-\*.c).
+
+**examples/rust** contains rust versions of the above C demo modules.
+
+**examples/docker** contains docker files for building Unit with WebAssembly
+support and the C examples.
+
+## Quickstart in Developing Rust WebAssembly Modules for Unit
+
+1) Have a suitable rust/wasm environment setup, See
+[Building the Rust libunit-wasm Crate and Examples](#building-the-rust-libunit-wasm-crate-and-examples) for some details
+
+2) Create a new rust project
+
+```
+$ cargo init --lib my-wasm-example
+```
+
+3) Add the [unit-wasm crate](https://crates.io/crates/unit-wasm) as dependency
+
+```
+$ cd my-wasm-example
+$ cargo add unit-wasm
+```
+
+4) Create the following _Cargo.toml_ file
+
+```
+[package]
+name = "my-wasm-example"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+unit-wasm = { version = "0.1.0" }
+
+[lib]
+crate-type = ["cdylib"]
+```
+
+5) Create an example application
+
+To do this you can simply take a copy of our echo-request demo in this
+repository
+
+```
+$ wget -O src/lib.rs https://raw.githubusercontent.com/nginx/unit-wasm/master/examples/rust/echo-request/src/lib.rs
+```
+
+6) Built it!
+
+```
+$ cargo build --target wasm32-wasi
+```
+
+You should now have a *target/wasm32-wasi/debug/my_wasm_example.wasm* file
+(yes, hyphens will be turned to underscores)
+
+You can now use this in Unit with the following application config snippet
+
+```JSON
+ "my-wasm-example": {
+ "type": "wasm",
+ "module": "/path/to/my-wasm-example/target/wasm32-wasi/debug/my_wasm_example.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "module_init_handler": "luw_module_init_handler",
+ "module_end_handler": "luw_module_end_handler"
+ },
+```
+
+7) Enjoy!
+
+## Getting Started
+
+### Requirements
+
+To build the C library and demo modules you will need make, clang, llvm,
+compiler-rt & lld 8.0+. (GCC does not currently have any support for
+WebAssembly).
+
+#### wasi-sysroot
+
+This is essentially a C library (based at least partly on cloudlibc and musl
+libc) for WebAssembly and is required for the _wasm32-wasi_ target.
+
+Distributions are starting to package this. On Fedora for example you can
+install the
+
+```
+wasi-libc-devel
+wasi-libc-static
+```
+
+packages.
+
+On FreeBSD you can install the
+
+```
+wasi-libc
+```
+
+package.
+
+The Makefiles will pick this up automatically.
+
+Where _wasi-sysroot_ is not available you can grab it from
+[here](https://github.com/WebAssembly/wasi-sdk/releases). Just grab the latest
+wasi-sysroot-VERSION.tar.gz tarball.
+
+Untar the wasi-sysroot package someplace.
+
+#### libclang_rt.builtins-wasm32-wasi
+
+You will probably also need to grab the latest
+libclang\_rt.builtins-wasm32-wasi-VERSION.tar.gz tarball from the same
+location and keep it handy.
+
+### Building libunit-wasm and C Examples
+
+Once you have the above sorted you can simply try doing
+
+```
+$ make WASI_SYSROOT=/path/to/wasi-sysroot examples
+```
+
+**NOTE:**
+
+The Makefiles will look for an already installed wasi-sysroot on Fedora &
+FreeBSD, so you may not need to specify it as above.
+
+If you do, you can set the WASI\_SYSROOT environment variable in your shell so
+you don't need to specify it here.
+
+This will attempt to build libunit-wasm and the two example WebAssembly
+modules, _luw-echo-request.wasm_ & _luw-upload-reflector.wasm_.
+
+If the above fails (which currently there is a good chance it will) with an
+error message like
+
+```
+wasm-ld: error: cannot open /usr/lib64/clang/16/lib/wasi/libclang_rt.builtins-wasm32.a: No such file or directory
+clang: error: linker command failed with exit code 1 (use -v to see invocation)
+```
+
+Then this is where that other tarball you downloaded comes in. Extract the
+*libclang\_rt.builtins-wasm32.a* from it and copy it into the location
+mentioned in the above error message.
+
+Try the make command again...
+
+### Building the Rust libunit-wasm Crate and Examples
+
+To build the rust stuff you will of course need rust and also cargo and the
+rust wasm/wasi stuff. On Fedora this is the relevant packages I have installed
+
+```
+cargo
+rust
+rust-std-static
+rust-std-static-wasm32-unknown-unknown
+rust-std-static-wasm32-wasi
+```
+
+Install with $PKGMGR.
+
+If you have also completed the above building of the C library and examples
+you should now be good to go.
+
+```
+$ make examples-rust
+```
+
+If you need to specify the *WASI_SYSROOT*, specify it in the make command as
+above.
+
+This will build the libunit-wasm rust crate and rust example modules.
+
+### Using With Unit
+
+Now that you have all the above built, you are now ready to test it out with
+Unit.
+
+If you created both the C and rust examples you will now have the following
+WebAssembly modules
+
+```
+examples/c/luw-echo-request.wasm
+examples/c/luw-upload-reflector.wasm
+examples/rust/echo-request/target/wasm32-wasi/debug/rust_echo_test.wasm
+examples/rust/upload-reflector/target/wasm32-wasi/debug/rust_upload_reflector.wasm
+```
+
+We won't go into the details of building Unit from source and enabling the
+Unit WebAssembly language module here (see the [HOWTO.md](https://github.com/nginx/unit-wasm/blob/master/HOWTO.md) in the repository root for more details) but will
+instead assume you already have a Unit with the WebAssembly language module
+already running.
+
+Create the following Unit config
+
+```JSON
+{
+ "listeners": {
+ "[::1]:8888": {
+ "pass": "routes"
+ }
+ },
+
+ "settings": {
+ "http": {
+ "max_body_size": 1073741824
+ }
+ },
+
+ "routes": [
+ {
+ "match": {
+ "uri": "/echo*"
+ },
+ "action": {
+ "pass": "applications/luw-echo-request"
+ }
+ },
+ {
+ "match": {
+ "uri": "/upload*"
+ },
+ "action": {
+ "pass": "applications/luw-upload-reflector"
+ }
+ },
+ {
+ "match": {
+ "uri": "/rust-echo*"
+ },
+ "action": {
+ "pass": "applications/rust-echo-test"
+ }
+ },
+ {
+ "match": {
+ "uri": "/rust-upload*"
+ },
+ "action": {
+ "pass": "applications/rust-upload-reflector"
+ }
+ }
+ ],
+
+ "applications": {
+ "luw-echo-request": {
+ "type": "wasm",
+ "module": "/path/to/unit-wasm/examples/c/luw-echo-request.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "module_init_handler": "luw_module_init_handler",
+ "module_end_handler": "luw_module_end_handler"
+ },
+ "luw-upload-reflector": {
+ "type": "wasm",
+ "module": "/path/to/unit-wasm/examples/c/luw-upload-reflector.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "request_end_handler": "luw_request_end_handler",
+ "response_end_handler": "luw_response_end_handler"
+ },
+ "rust-echo-test": {
+ "type": "wasm",
+ "module": "/path/to/unit-wasm/examples/rust/echo-request/target/wasm32-wasi/debug/rust_echo_test.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "module_init_handler": "luw_module_init_handler",
+ "module_end_handler": "luw_module_end_handler"
+ },
+ "rust-upload-reflector": {
+ "type": "wasm",
+ "module": "/path/to/unit-wasm/examples/rust/upload-reflector/rust_upload_reflector.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "request_end_handler": "luw_request_end_handler",
+ "response_end_handler": "luw_response_end_handler"
+ }
+ }
+}
+```
+
+Load this config then you should be ready to try it.
+
+```shell
+$ curl -X POST -d "Hello World" --cookie "mycookie=hmmm" http://localhost:8888/echo/?q=a
+ *** Welcome to WebAssembly on Unit! [libunit-wasm (0.1.0/0x00010000)] ***
+
+[Request Info]
+REQUEST_PATH = /echo/?q=a
+METHOD = POST
+VERSION = HTTP/1.1
+QUERY = q=a
+REMOTE = ::1
+LOCAL_ADDR = ::1
+LOCAL_PORT = 8080
+SERVER_NAME = localhost
+
+[Request Headers]
+Host = localhost:8080
+User-Agent = curl/8.0.1
+Accept = */*
+Cookie = mycookie=hmmm
+Content-Length = 11
+Content-Type = application/x-www-form-urlencoded
+
+[POST data]
+Hello World
+```
+
+```shell
+$ curl -v -X POST --data-binary @audio.flac -H "Content-Type: audio/flac" http://localhost:8888/upload-reflector/ -o wasm-test.dat
+...
+> Content-Type: audio/flac
+> Content-Length: 60406273
+...
+ % Total % Received % Xferd Average Speed Time Time Time Current
+ Dload Upload Total Spent Left Speed
+100 115M 100 57.6M 100 57.6M 47.6M 47.6M 0:00:01 0:00:01 --:--:-- 95.2M
+...
+< Content-Type: audio/flac
+< Content-Length: 60406273
+...
+$ sha1sum audio.flac wasm-test.dat
+ef5c9c228544b237022584a8ac4612005cd6263e audio.flac
+ef5c9c228544b237022584a8ac4612005cd6263e wasm-test.dat
+```
+
+## Consuming the C Library
+
+If **unit/unit-wasm.h** and **libunit.a** are installed into standard
+include/library directories then
+
+Include the libunit-wasm header file
+
+```C
+....
+#include <unit/unit-wasm.h>
+...
+```
+
+Link against libunit-wasm
+
+```
+$ clang ... -o myapp.wasm myapp.c -lunit-wasm
+```
+
+See [API-C.md](https://github.com/nginx/unit-wasm/blob/master/API-C.md) for an
+overview of the API.
+
+## License
+
+This project is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 0000000..19e1bce
--- /dev/null
+++ b/examples/.gitignore
@@ -0,0 +1 @@
+*.wasm
diff --git a/examples/c/Makefile b/examples/c/Makefile
new file mode 100644
index 0000000..1b10269
--- /dev/null
+++ b/examples/c/Makefile
@@ -0,0 +1,40 @@
+include ../../shared.mk
+
+CFLAGS += -I../../src/c/include
+LIBS = -L../../src/c -lunit-wasm
+
+SDIR = examples/c
+
+LUW_SRCDIR = ../../src/c
+
+luw_deps = $(LUW_SRCDIR)/libunit-wasm.a \
+ $(LUW_SRCDIR)/include/unit/unit-wasm.h
+
+examples: examples-luw
+
+examples-luw: luw-echo-request.wasm luw-upload-reflector.wasm
+
+examples-raw: echo-request-raw.wasm upload-reflector-raw.wasm
+
+luw-echo-request.wasm: luw-echo-request.c $(luw_deps)
+ $(PP_CCLNK) $(SDIR)/$@
+ $(v)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS)
+
+luw-upload-reflector.wasm: luw-upload-reflector.c $(luw_deps)
+ $(PP_CCLNK) $(SDIR)/$@
+ $(v)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS)
+
+unit-wasm-raw.o: unit-wasm-raw.c unit-wasm-raw.h
+ $(PP_CC) $(SDIR)/$@
+ $(v)$(CC) $(CFLAGS) -c $<
+
+echo-request-raw.wasm: echo-request-raw.c unit-wasm-raw.o
+ $(PP_CCLNK) $(SDIR)/$@
+ $(v)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< unit-wasm-raw.o
+
+upload-reflector-raw.wasm: upload-reflector-raw.c unit-wasm-raw.o
+ $(PP_CCLNK) $(SDIR)/$@
+ $(v)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< unit-wasm-raw.o
+
+clean:
+ rm -f *.wasm *.o *.gch
diff --git a/examples/c/echo-request-raw.c b/examples/c/echo-request-raw.c
new file mode 100644
index 0000000..2071597
--- /dev/null
+++ b/examples/c/echo-request-raw.c
@@ -0,0 +1,187 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * Copyright (C) Andrew Clayton
+ * Copyright (C) F5, Inc.
+ */
+
+/*
+ * echo-request-raw.c - Raw example of writing a WASM module for use with Unit
+ *
+ * Download the wasi-sysroot tarball from https://github.com/WebAssembly/wasi-sdk/releases
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "unit-wasm-raw.h"
+
+static u8 *request_buf;
+
+__attribute__((import_module("env"), import_name("nxt_wasm_get_init_mem_size")))
+u32 nxt_wasm_get_init_mem_size(void);
+__attribute__((import_module("env"), import_name("nxt_wasm_response_end")))
+void nxt_wasm_response_end(void);
+__attribute__((import_module("env"), import_name("nxt_wasm_send_response")))
+void nxt_wasm_send_response(u32 offset);
+
+__attribute__((export_name("wasm_module_end_handler")))
+void wasm_module_end_handler(void)
+{
+ free(request_buf);
+}
+
+__attribute__((export_name("wasm_module_init_handler")))
+void wasm_module_init_handler(void)
+{
+ request_buf = malloc(nxt_wasm_get_init_mem_size());
+}
+
+__attribute__((export_name("wasm_free_handler")))
+void wasm_free_handler(u32 addr)
+{
+ free((void *)addr);
+}
+
+__attribute__((export_name("wasm_malloc_handler")))
+u32 wasm_malloc_handler(size_t size)
+{
+ return (u32)malloc(size);
+}
+
+static int echo_request(u8 *addr)
+{
+ u8 *p;
+ const char *method;
+ struct req *req;
+ struct resp *resp;
+ struct hdr_field *hf;
+ struct hdr_field *hf_end;
+ static const int resp_offs = 4096;
+
+ printf("==[WASM RESP]== %s:\n", __func__);
+
+ /*
+ * For convenience, we will return our headers at the start
+ * of the shared memory so leave a little space (resp_offs)
+ * before storing the main response.
+ *
+ * send_headers() will return the start of the shared memory,
+ * echo_request() will return the start of the shared memory
+ * plus resp_offs.
+ */
+ resp = (struct resp *)(addr + resp_offs);
+
+ req = (struct req *)request_buf;
+
+#define BUF_ADD(name, member) \
+ do { \
+ p = mempcpy(p, name, strlen(name)); \
+ p = mempcpy(p, (u8 *)req + req->member##_offs, req->member##_len); \
+ p = mempcpy(p, "\n", 1); \
+ } while (0)
+
+#define BUF_ADD_HF() \
+ do { \
+ p = mempcpy(p, (u8 *)req + hf->name_offs, hf->name_len); \
+ p = mempcpy(p, " = ", 3); \
+ p = mempcpy(p, (u8 *)req + hf->value_offs, hf->value_len); \
+ p = mempcpy(p, "\n", 1); \
+ } while (0)
+
+ p = resp->data;
+
+ p = mempcpy(p, "Welcome to WebAssembly on Unit!\n\n", 33);
+
+ p = mempcpy(p, "[Request Info]\n", 15);
+ BUF_ADD("REQUEST_PATH = ", path);
+ BUF_ADD("METHOD = ", method);
+ BUF_ADD("VERSION = ", version);
+ BUF_ADD("QUERY = ", query);
+ BUF_ADD("REMOTE = ", remote);
+ BUF_ADD("LOCAL_ADDR = ", local_addr);
+ BUF_ADD("LOCAL_PORT = ", local_port);
+ BUF_ADD("SERVER_NAME = ", server_name);
+
+ p = mempcpy(p, "\n[Request Headers]\n", 19);
+ hf_end = req->fields + req->nr_fields;
+ for (hf = req->fields; hf < hf_end; hf++)
+ BUF_ADD_HF();
+
+ method = (char *)req + req->method_offs;
+ if (memcmp(method, "POST", req->method_len) == 0 ||
+ memcmp(method, "PUT", req->method_len) == 0) {
+ p = mempcpy(p, "\n[", 2);
+ p = mempcpy(p, method, req->method_len);
+ p = mempcpy(p, " data]\n", 7);
+ p = mempcpy(p, (u8 *)req + req->content_offs, req->content_len);
+ p = mempcpy(p, "\n", 1);
+ }
+
+ p = memcpy(p, "\0", 1);
+
+ resp->size = p - resp->data;
+
+ send_headers(addr, "text/plain", resp->size);
+
+ nxt_wasm_send_response(resp_offs);
+ /* Tell Unit no more data to send */
+ nxt_wasm_response_end();
+
+ return 0;
+}
+
+__attribute__((export_name("wasm_request_handler")))
+int wasm_request_handler(u8 *addr)
+{
+ struct req *req = (struct req *)addr;
+ struct req *rb = (struct req *)request_buf;
+
+ printf("==[WASM REQ]== %s:\n", __func__);
+
+ /*
+ * This function _may_ be called multiple times during a single
+ * request if there is a large amount of data to transfer.
+ *
+ * In this simple demo, we are only expecting it to be called
+ * once per request.
+ *
+ * Some useful request meta data:
+ *
+ * req->content_len contains the overall size of the POST/PUT
+ * data.
+ * req->content_sent shows how much of the body content has been
+ * in _this_ request.
+ * req->total_content_sent shows how much of it has been sent in
+ * total.
+ * req->content_offs is the offset in the passed in memory where
+ * the body content starts.
+ *
+ * For new requests req->request_size shows the total size of
+ * _this_ request, incl the req structure itself.
+ * For continuation requests, req->request_size is just the amount
+ * of new content, i.e req->content_sent
+ *
+ * When req->content_len == req->total_content_sent, that's the end
+ * of that request.
+ */
+
+ printf("==[WASM REQ]== req->request_size : %u\n", req->request_size);
+ memcpy(request_buf, addr, req->request_size);
+
+ rb = (struct req *)request_buf;
+ printf("==[WASM REQ]== rb@%p\n", rb);
+ printf("==[WASM REQ]== request_buf@%p\n", request_buf);
+ printf("==[WASM REQ]== rb->content_offs : %u\n", rb->content_offs);
+ printf("==[WASM REQ]== rb->content_len : %u\n", rb->content_len);
+ printf("==[WASM REQ]== rb->content_sent : %u\n", rb->content_sent);
+ printf("==[WASM REQ]== rb->request_size : %u\n", rb->request_size);
+
+ echo_request(addr);
+
+ return 0;
+}
diff --git a/examples/c/luw-echo-request.c b/examples/c/luw-echo-request.c
new file mode 100644
index 0000000..5655c65
--- /dev/null
+++ b/examples/c/luw-echo-request.c
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * examples/c/luw-echo-request.c - Example of writing a WASM module for use
+ * with Unit using libunit-wasm
+ *
+ * Copyright (C) Andrew Clayton
+ * Copyright (C) F5, Inc.
+ */
+
+#define _XOPEN_SOURCE 500
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "unit/unit-wasm.h"
+
+static u8 *request_buf;
+
+__luw_export_name("luw_module_end_handler")
+void luw_module_end_handler(void)
+{
+ free(request_buf);
+}
+
+__luw_export_name("luw_module_init_handler")
+void luw_module_init_handler(void)
+{
+ request_buf = malloc(luw_mem_get_init_size());
+}
+
+static bool hdr_iter_func(luw_ctx_t *ctx, const char *name, const char *value,
+ void *user_data __luw_unused)
+{
+ luw_mem_writep(ctx, "%s = %s\n", name, value);
+
+ return true;
+}
+
+__luw_export_name("luw_request_handler")
+int luw_request_handler(u8 *addr)
+{
+ luw_ctx_t ctx;
+ char clen[32];
+ const char *method;
+
+ luw_init_ctx(&ctx, addr, 4096 /* Response offset */);
+ /* Take a copy of the request and use that */
+ luw_set_req_buf(&ctx, &request_buf, LUW_SRB_NONE);
+
+#define BUF_ADD(fmt, member) \
+ luw_mem_writep(&ctx, fmt, luw_get_http_##member(&ctx));
+
+ luw_mem_writep(&ctx,
+ " *** Welcome to WebAssembly on Unit! "
+ "[libunit-wasm (%d.%d.%d/%#0.8x)] ***\n\n",
+ LUW_VERSION_MAJOR, LUW_VERSION_MINOR, LUW_VERSION_PATCH,
+ LUW_VERSION_NUMBER);
+
+ luw_mem_writep(&ctx, "[Request Info]\n");
+ BUF_ADD("REQUEST_PATH = %s\n", path);
+ BUF_ADD("METHOD = %s\n", method);
+ BUF_ADD("VERSION = %s\n", version);
+ BUF_ADD("QUERY = %s\n", query);
+ BUF_ADD("REMOTE = %s\n", remote);
+ BUF_ADD("LOCAL_ADDR = %s\n", local_addr);
+ BUF_ADD("LOCAL_PORT = %s\n", local_port);
+ BUF_ADD("SERVER_NAME = %s\n", server_name);
+
+ luw_mem_writep(&ctx, "\n[Request Headers]\n");
+
+ luw_http_hdr_iter(&ctx, hdr_iter_func, NULL);
+
+ method = luw_get_http_method(&ctx);
+ if (memcmp(method, "POST", strlen(method)) == 0 ||
+ memcmp(method, "PUT", strlen(method)) == 0) {
+ luw_mem_writep(&ctx, "\n[%s data]\n", method);
+ luw_mem_writep_data(&ctx, luw_get_http_content(&ctx),
+ luw_get_http_content_len(&ctx));
+ luw_mem_writep(&ctx, "\n");
+ }
+
+ luw_http_init_headers(&ctx, 2, 0);
+
+ snprintf(clen, sizeof(clen), "%lu", luw_get_response_data_size(&ctx));
+ luw_http_add_header(&ctx, 0, "Content-Type", "text/plain");
+ luw_http_add_header(&ctx, 1, "Content-Length", clen);
+
+ luw_http_send_headers(&ctx);
+
+ luw_http_send_response(&ctx);
+ /* Tell Unit no more data to send */
+ luw_http_response_end();
+
+ return 0;
+}
diff --git a/examples/c/luw-upload-reflector.c b/examples/c/luw-upload-reflector.c
new file mode 100644
index 0000000..95bc514
--- /dev/null
+++ b/examples/c/luw-upload-reflector.c
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * examples/c/luw-upload-reflector.c - Example of writing a WASM module for
+ * use with Unit using libunit-wasm
+ *
+ * Copyright (C) Andrew Clayton
+ * Copyright (C) F5, Inc.
+ */
+
+#define _XOPEN_SOURCE 500
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "unit/unit-wasm.h"
+
+static luw_ctx_t ctx;
+
+static size_t total_response_sent;
+
+static u8 *request_buf;
+
+/*
+ * While these first two _handlers_ aren't technically required, they
+ * could be combined or the code could just go in upload_reflector(),
+ * they demonstrate their use in ensuring the module is in the right
+ * state for a new request.
+ */
+__luw_export_name("luw_response_end_handler")
+void luw_response_end_handler(void)
+{
+ total_response_sent = 0;
+}
+
+__luw_export_name("luw_request_end_handler")
+void luw_request_end_handler(void)
+{
+ if (!request_buf)
+ return;
+
+ free(request_buf);
+ request_buf = NULL;
+}
+
+static int upload_reflector(luw_ctx_t *ctx)
+{
+ size_t write_bytes;
+
+ /* Send headers */
+ if (total_response_sent == 0) {
+ static const char *defct = "application/octet-stream";
+ const char *ct = luw_http_hdr_get_value(ctx, "Content-Type");
+ char clen[32];
+
+ snprintf(clen, sizeof(clen), "%lu",
+ luw_get_http_content_len(ctx));
+
+ luw_http_init_headers(ctx, 2, 0);
+ luw_http_add_header(ctx, 0, "Content-Type", ct ? ct : defct);
+ luw_http_add_header(ctx, 1, "Content-Length", clen);
+ luw_http_send_headers(ctx);
+ }
+
+ write_bytes = luw_mem_fill_buf_from_req(ctx, total_response_sent);
+ total_response_sent += write_bytes;
+
+ luw_http_send_response(ctx);
+
+ if (total_response_sent == luw_get_http_content_len(ctx)) {
+ /* Tell Unit no more data to send */
+ luw_http_response_end();
+ }
+
+ return 0;
+}
+
+__luw_export_name("luw_request_handler")
+int luw_request_handler(u8 *addr)
+{
+ if (!request_buf) {
+ luw_init_ctx(&ctx, addr, 0 /* Response offset */);
+ /*
+ * Take a copy of the request and use that, we do this
+ * in APPEND mode so we can build up request_buf from
+ * multiple requests.
+ *
+ * Just allocate memory for the total amount of data we
+ * expect to get, this includes the request structure
+ * itself as well as any body content.
+ */
+ luw_set_req_buf(&ctx, &request_buf,
+ LUW_SRB_APPEND|LUW_SRB_ALLOC|LUW_SRB_FULL_SIZE);
+ } else {
+ luw_req_buf_append(&ctx, addr);
+ }
+
+ upload_reflector(&ctx);
+
+ return 0;
+}
diff --git a/examples/c/unit-wasm-raw.c b/examples/c/unit-wasm-raw.c
new file mode 100644
index 0000000..42ebcbf
--- /dev/null
+++ b/examples/c/unit-wasm-raw.c
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * Copyright (C) Andrew Clayton
+ * Copyright (C) F5, Inc.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <string.h>
+
+#include "unit-wasm-raw.h"
+
+__attribute__((import_module("env"), import_name("nxt_wasm_send_headers")))
+void nxt_wasm_send_headers(u32 offset);
+
+void send_headers(u8 *addr, const char *ct, size_t len)
+{
+ struct resp_hdr *rh;
+ char clen[32];
+ u8 *p;
+ static const u32 hdr_offs = 0;
+
+ rh = (struct resp_hdr *)addr;
+
+#define SET_HDR_FIELD(idx, name, val) \
+ do { \
+ rh->fields[idx].name_offs = p - addr; \
+ rh->fields[idx].name_len = strlen(name); \
+ p = mempcpy(p, name, rh->fields[idx].name_len); \
+ rh->fields[idx].value_offs = p - addr; \
+ rh->fields[idx].value_len = strlen(val); \
+ p = mempcpy(p, val, rh->fields[idx].value_len); \
+ } while (0)
+
+ rh->nr_fields = 2;
+ p = addr + sizeof(struct resp_hdr) +
+ (rh->nr_fields * sizeof(struct hdr_field));
+
+ SET_HDR_FIELD(0, "Content-Type", ct);
+ snprintf(clen, sizeof(clen), "%lu", len);
+ SET_HDR_FIELD(1, "Content-Length", clen);
+
+ nxt_wasm_send_headers(hdr_offs);
+}
diff --git a/examples/c/unit-wasm-raw.h b/examples/c/unit-wasm-raw.h
new file mode 100644
index 0000000..6fd9d35
--- /dev/null
+++ b/examples/c/unit-wasm-raw.h
@@ -0,0 +1,87 @@
+#ifndef _UNIT_WASM_H_
+#define _UNIT_WASM_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef uint64_t u64;
+typedef int64_t s64;
+typedef uint32_t u32;
+typedef int32_t s32;
+typedef uint16_t u16;
+typedef int16_t s16;
+typedef uint8_t u8;
+typedef int8_t s8;
+
+#ifndef __unused
+#define __unused __attribute__((unused))
+#endif
+#ifndef __maybe_unused
+#define __maybe_unused __unused
+#endif
+#ifndef __always_unused
+#define __always_unused __unused
+#endif
+
+struct hdr_field {
+ u32 name_offs;
+ u32 name_len;
+ u32 value_offs;
+ u32 value_len;
+};
+
+struct req {
+ u32 method_offs;
+ u32 method_len;
+ u32 version_offs;
+ u32 version_len;
+ u32 path_offs;
+ u32 path_len;
+ u32 query_offs;
+ u32 query_len;
+ u32 remote_offs;
+ u32 remote_len;
+ u32 local_addr_offs;
+ u32 local_addr_len;
+ u32 local_port_offs;
+ u32 local_port_len;
+ u32 server_name_offs;
+ u32 server_name_len;
+
+ u32 content_offs;
+ u32 content_len;
+ u32 content_sent;
+ u32 total_content_sent;
+
+ u32 request_size;
+
+ u32 nr_fields;
+
+ u32 tls;
+
+ struct hdr_field fields[];
+};
+
+struct resp {
+ u32 size;
+
+ u8 data[];
+};
+
+struct resp_hdr {
+ u32 nr_fields;
+
+ struct hdr_field fields[];
+};
+
+extern void wasm_module_end_handler(void);
+extern void wasm_module_init_handler(void);
+extern void wasm_response_end_handler(void);
+extern void wasm_request_end_handler(void);
+extern void wasm_free_handler(u32 addr);
+extern u32 wasm_malloc_handler(size_t size);
+extern int wasm_request_handler(u8 *addr);
+
+extern void send_headers(u8 *addr, const char *ct, size_t len);
+
+#endif /* _UNIT_WASM_H_ */
diff --git a/examples/c/upload-reflector-raw.c b/examples/c/upload-reflector-raw.c
new file mode 100644
index 0000000..3da4f8d
--- /dev/null
+++ b/examples/c/upload-reflector-raw.c
@@ -0,0 +1,223 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * Copyright (C) Andrew Clayton
+ * Copyright (C) F5, Inc.
+ */
+
+/*
+ * upload-reflector-raw.c - Raw example of writing a WASM module for use with
+ * Unit
+ *
+ * Download the wasi-sysroot tarball from https://github.com/WebAssembly/wasi-sdk/releases
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "unit-wasm-raw.h"
+
+static size_t total_response_sent;
+
+static u8 *request_buf;
+
+__attribute__((import_module("env"), import_name("nxt_wasm_get_init_mem_size")))
+u32 nxt_wasm_get_init_mem_size(void);
+__attribute__((import_module("env"), import_name("nxt_wasm_response_end")))
+void nxt_wasm_response_end(void);
+__attribute__((import_module("env"), import_name("nxt_wasm_send_response")))
+void nxt_wasm_send_response(u32 offset);
+
+__attribute__((export_name("wasm_response_end_handler")))
+void wasm_response_end_handler(void)
+{
+ total_response_sent = 0;
+}
+
+__attribute__((export_name("wasm_request_end_handler")))
+void wasm_request_end_handler(void)
+{
+ if (!request_buf)
+ return;
+
+ free(request_buf);
+ request_buf = NULL;
+}
+
+__attribute__((export_name("wasm_free_handler")))
+void wasm_free_handler(u32 addr)
+{
+ free((void *)addr);
+}
+
+__attribute__((export_name("wasm_malloc_handler")))
+u32 wasm_malloc_handler(size_t size)
+{
+ return (u32)malloc(size);
+}
+
+static int upload_reflector(u8 *addr)
+{
+ size_t mem_size = nxt_wasm_get_init_mem_size();
+ size_t rsize = sizeof(struct resp);
+ size_t write_bytes;
+ struct req *req;
+ struct resp *resp;
+
+ printf("==[WASM RESP]== %s:\n", __func__);
+
+ resp = (struct resp *)addr;
+ req = (struct req *)request_buf;
+
+ printf("==[WASM RESP]== resp@%p\n", resp);
+ printf("==[WASM RESP]== req@%p\n", req);
+ printf("==[WASM RESP]== req->content_len : %u\n", req->content_len);
+
+ resp = (struct resp *)addr;
+
+ /* Send headers */
+ if (total_response_sent == 0) {
+ const char *field;
+ struct hdr_field *f;
+ struct hdr_field *f_end;
+ char ct[256];
+
+ /* Try to set the Content-Type */
+ f_end = req->fields + req->nr_fields;
+ for (f = req->fields; f < f_end; f++) {
+ field = (const char *)(u8 *)req + f->name_offs;
+
+ if (strncasecmp(field, "Content-Type", 12) == 0) {
+ snprintf(ct, sizeof(ct), "%.*s", f->value_len,
+ (u8 *)req + f->value_offs);
+ break;
+ }
+
+ field = NULL;
+ }
+ if (!field)
+ sprintf(ct, "application/octet-stream");
+
+ send_headers(addr, ct, req->content_len);
+ }
+
+ write_bytes = req->content_sent;
+ if (write_bytes > mem_size - rsize)
+ write_bytes = mem_size - rsize;
+
+ printf("==[WASM RESP]== write_bytes : %lu\n", write_bytes);
+ printf("==[WASM RESP]== req->content_len : %u\n", req->content_len);
+ printf("==[WASM RESP]== total_response_sent : %lu\n",
+ total_response_sent);
+
+ printf("==[WASM RESP]== Copying (%lu) bytes of data from [%p+%lx] to "
+ "[%p]\n", write_bytes, req,
+ req->content_offs + total_response_sent, resp->data);
+ memcpy(resp->data,
+ (u8 *)req + req->content_offs + total_response_sent,
+ write_bytes);
+
+ total_response_sent += write_bytes;
+ resp->size = write_bytes;
+ printf("==[WASM RESP]== resp->size : %u\n", resp->size);
+
+ nxt_wasm_send_response(0);
+
+ if (total_response_sent == req->content_len) {
+ printf("==[WASM RESP]== All data sent. Cleaning up...\n");
+ total_response_sent = 0;
+
+ free(request_buf);
+ request_buf = NULL;
+
+ /* Tell Unit no more data to send */
+ nxt_wasm_response_end();
+ }
+
+ return 0;
+}
+
+__attribute__((export_name("wasm_request_handler")))
+int wasm_request_handler(u8 *addr)
+{
+ struct req *req = (struct req *)addr;
+ struct req *rb = (struct req *)request_buf;
+
+ printf("==[WASM REQ]== %s:\n", __func__);
+
+ /*
+ * This function _may_ be called multiple times during a single
+ * request if there is a large amount of data to transfer.
+ *
+ * Some useful request meta data:
+ *
+ * req->content_len contains the overall size of the POST/PUT
+ * data.
+ * req->content_sent shows how much of the body content has been
+ * in _this_ request.
+ * req->total_content_sent shows how much of it has been sent in
+ * total.
+ * req->content_offs is the offset in the passed in memory where
+ * the body content starts.
+ *
+ * For new requests req->request_size shows the total size of
+ * _this_ request, incl the req structure itself.
+ * For continuation requests, req->request_size is just the amount
+ * of new content, i.e req->content_sent
+ *
+ * When req->content_len == req->total_content_sent, that's the end
+ * of that request.
+ */
+
+ if (!request_buf) {
+ /*
+ * Just allocate memory for the total amount of data we
+ * expect to get, this includes the request structure
+ * itself as well as any body content.
+ */
+ printf("==[WASM REQ]== malloc(%u)\n",
+ req->content_offs + req->content_len);
+ request_buf = malloc(req->content_offs + req->content_len);
+
+ /*
+ * Regardless of how much memory we allocated above, here
+ * we only want to copy the amount of data we actually
+ * received in this request.
+ */
+ printf("==[WASM REQ]== req->request_size : %u\n",
+ req->request_size);
+ memcpy(request_buf, addr, req->request_size);
+
+ rb = (struct req *)request_buf;
+ printf("==[WASM REQ]== rb@%p\n", rb);
+ printf("==[WASM REQ]== request_buf@%p\n", request_buf);
+ printf("==[WASM REQ]== rb->content_offs : %u\n",
+ rb->content_offs);
+ printf("==[WASM REQ]== rb->content_len : %u\n",
+ rb->content_len);
+ printf("==[WASM REQ]== rb->content_sent : %u\n",
+ rb->content_sent);
+ printf("==[WASM REQ]== rb->request_size : %u\n",
+ rb->request_size);
+ } else {
+ memcpy(request_buf + rb->request_size, addr + req->content_offs,
+ req->request_size);
+
+ printf("==[WASM REQ +]== req->content_offs : %u\n",
+ req->content_offs);
+ printf("==[WASM REQ +]== req->content_sent : %u\n",
+ req->content_sent);
+ printf("==[WASM REQ +]== req->request_size : %u\n",
+ req->request_size);
+
+ rb->content_sent = req->content_sent;
+ rb->total_content_sent = req->total_content_sent;
+ }
+
+ upload_reflector(addr);
+
+ return 0;
+}
diff --git a/examples/docker/README.md b/examples/docker/README.md
new file mode 100644
index 0000000..61d2740
--- /dev/null
+++ b/examples/docker/README.md
@@ -0,0 +1,31 @@
+Unit-Wasm demo
+==============
+
+## Build the docker images
+
+From the repository root, run
+
+```shell
+$ make docker
+```
+
+This builds two docker images.
+
+### 1. unit:wasm
+
+This image is based on the Docker Official Images for Unit 1.30 with a fresh
+build of unitd and the experimental Wasm module. Wasmtime is included as a
+shared object.
+
+### 2. unit:demo-wasm
+
+This image is based on the new `unit:wasm` image created above. It includes
+a demo application written in C and compiled to wasm.
+
+## Run the demo
+
+```shell
+$ docker run -d -p 9000:80 unit:demo-wasm
+$ curl localhost:9000
+$ curl localhost:9000/echo
+```
diff --git a/examples/docker/demo-wasm.Dockerfile b/examples/docker/demo-wasm.Dockerfile
new file mode 100644
index 0000000..2385211
--- /dev/null
+++ b/examples/docker/demo-wasm.Dockerfile
@@ -0,0 +1,20 @@
+FROM unit:wasm AS build
+WORKDIR /demo
+
+# Get all the build tools we need
+#
+RUN apt update && apt install -y wget build-essential clang llvm lld
+RUN cd /usr/lib/llvm-11/lib/clang/11.0.1 && wget -O- https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/libclang_rt.builtins-wasm32-wasi-20.0.tar.gz | tar zxvf -
+RUN wget -O- https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sysroot-20.0.tar.gz | tar zxfv -
+
+# Copy-in the demo application source code and build into a .wasm module
+#
+ADD ${PWD} /demo/
+RUN make WASI_SYSROOT=/demo/wasi-sysroot examples
+
+# Copy the .wasm modules and Unit configuration to the final Docker image
+# that will run the demo application.
+#
+FROM unit:wasm
+COPY --from=build /demo/examples/c/*.wasm /demo/
+ADD examples/docker/wasm-conf.json /docker-entrypoint.d
diff --git a/examples/docker/unit-wasm.Dockerfile b/examples/docker/unit-wasm.Dockerfile
new file mode 100644
index 0000000..b7b47a5
--- /dev/null
+++ b/examples/docker/unit-wasm.Dockerfile
@@ -0,0 +1,40 @@
+# Start with the minimal Docker Official Image so we can use the same defaults
+#
+FROM unit:minimal AS build
+WORKDIR /src
+
+# Get all the build tools we need, including Wasmtime
+#
+#RUN apt update && apt install -y wget git build-essential clang lld libpcre2-dev libssl-dev
+RUN apt update && apt install -y wget git build-essential libpcre2-dev libssl-dev
+RUN wget -O- https://github.com/bytecodealliance/wasmtime/releases/download/v11.0.0/wasmtime-v11.0.0-$(arch)-linux-c-api.tar.xz \
+ | tar Jxfv - && \
+ mkdir /usr/lib/wasmtime && \
+ cp /src/wasmtime-v11.0.0-$(arch)-linux-c-api/lib/* /usr/lib/wasmtime
+
+# Build NGINX JavaScript (njs) so that we have a feature-complete Unit
+#
+RUN git clone https://github.com/nginx/njs.git && \
+ cd njs && \
+ ./configure --no-libxml2 --no-zlib && \
+ make
+
+# Build Unit with the Wasm module, copying the configure arguments from the
+# official image.
+#
+RUN git clone https://github.com/nginx/unit.git && \
+ cd unit && \
+ wget -O- https://github.com/nginx/unit/pull/902.patch | patch -p1 && \
+ ./configure $(unitd --version 2>&1 | tr ' ' '\n' | grep ^-- | grep -v opt=) \
+ --cc-opt="-I/src/njs/src -I/src/njs/build" --ld-opt=-L/src/njs/build && \
+ ./configure wasm --include-path=/src/wasmtime-v11.0.0-$(arch)-linux-c-api/include \
+ --lib-path=/usr/lib/wasmtime --rpath && \
+ make
+
+# Create a clean final image by copying over only Wasmtime, the new unitd
+# binary, and the Wasm module.
+#
+FROM unit:minimal
+COPY --from=build /src/unit/build/sbin/unitd /usr/sbin
+COPY --from=build /src/unit/build/lib/unit/modules/wasm.unit.so /usr/lib/unit/modules
+COPY --from=build /usr/lib/wasmtime/*.so /usr/lib/wasmtime/
diff --git a/examples/docker/wasm-conf.json b/examples/docker/wasm-conf.json
new file mode 100644
index 0000000..5ed173e
--- /dev/null
+++ b/examples/docker/wasm-conf.json
@@ -0,0 +1,76 @@
+{
+ "access_log": "/dev/stdout",
+ "settings": {
+ "http": {
+ "log_route": true,
+ "max_body_size": 1073741824
+ }
+ },
+
+ "listeners": {
+ "*:80": {
+ "pass": "routes"
+ }
+ },
+
+ "routes": [
+ {
+ "match": {
+ "uri": "/echo*"
+ },
+ "action": {
+ "pass": "applications/luw-echo-request"
+ }
+ },
+ {
+ "match": {
+ "uri": "/upload*"
+ },
+ "action": {
+ "pass": "applications/luw-upload-reflector"
+ }
+ },
+ {
+ "match": {
+ "headers": {
+ "accept": "*text/html*"
+ }
+ },
+ "action": {
+ "share": "/usr/share/unit/welcome/welcome.html"
+ }
+ },
+ {
+ "action": {
+ "share": "/usr/share/unit/welcome/welcome.md"
+ }
+ }
+ ],
+
+ "applications": {
+ "luw-echo-request": {
+ "type": "wasm",
+ "module": "/demo/luw-echo-request.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "module_init_handler": "luw_module_init_handler",
+ "module_end_handler": "luw_module_end_handler",
+ "access": {
+ "filesystem": [
+ "/tmp",
+ "/var/tmp"
+ ]
+ }
+ },
+ "luw-upload-reflector": {
+ "type": "wasm",
+ "module": "/demo/luw-upload-reflector.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "request_end_handler": "luw_request_end_handler",
+ "response_end_handler": "luw_response_end_handler"
+ }
+ }
+}
diff --git a/examples/rust/.gitignore b/examples/rust/.gitignore
new file mode 100644
index 0000000..559d663
--- /dev/null
+++ b/examples/rust/.gitignore
@@ -0,0 +1,3 @@
+Cargo.lock
+
+target/
diff --git a/examples/rust/Makefile b/examples/rust/Makefile
new file mode 100644
index 0000000..b41bc33
--- /dev/null
+++ b/examples/rust/Makefile
@@ -0,0 +1,17 @@
+include ../../shared.mk
+
+SDIR = examples/rust
+
+examples: rust-echo-request rust-upload-reflector
+
+rust-echo-request: echo-request/Cargo.toml echo-request/src/lib.rs
+ $(PP_GEN) $(SDIR)/echo-request/target/wasm32-wasi/
+ $(v)cd echo-request; cargo build --target=wasm32-wasi
+
+rust-upload-reflector: upload-reflector/Cargo.toml upload-reflector/src/lib.rs
+ $(PP_GEN) $(SDIR)/upload-reflector/target/wasm32-wasi/
+ $(v)cd upload-reflector; cargo build --target=wasm32-wasi
+
+clean:
+ rm -f echo-request/Cargo.lock upload-reflector/Cargo.lock
+ rm -rf echo-request/target upload-reflector/target
diff --git a/examples/rust/echo-request/Cargo.toml b/examples/rust/echo-request/Cargo.toml
new file mode 100644
index 0000000..256242e
--- /dev/null
+++ b/examples/rust/echo-request/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "rust-echo-request"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+unit-wasm = { path = "../../../src/rust", version = "0.1.0-beta" }
+
+[lib]
+crate-type = ["cdylib"]
diff --git a/examples/rust/echo-request/src/lib.rs b/examples/rust/echo-request/src/lib.rs
new file mode 100644
index 0000000..4802cff
--- /dev/null
+++ b/examples/rust/echo-request/src/lib.rs
@@ -0,0 +1,200 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * Copyright (C) Andrew Clayton
+ * Copyright (C) Timo Stark
+ * Copyright (C) F5, Inc.
+ */
+
+// Include RAW FFI Bindings.
+// @todo: Replace this with the new native Rust API
+use unit_wasm::ffi::*;
+
+use std::ffi::CStr;
+use std::os::raw::c_char;
+use std::os::raw::c_void;
+use std::ptr;
+
+// Buffer of some size to store the copy of the request
+static mut REQUEST_BUF: *mut u8 = ptr::null_mut();
+
+#[no_mangle]
+pub extern "C" fn luw_module_end_handler() {
+ unsafe {
+ luw_free(REQUEST_BUF as *mut c_void);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn luw_module_init_handler() {
+ unsafe {
+ REQUEST_BUF = luw_malloc(luw_mem_get_init_size().try_into().unwrap())
+ as *mut u8;
+ }
+}
+
+pub extern "C" fn hdr_iter_func(
+ ctx: *mut luw_ctx_t,
+ name: *const c_char,
+ value: *const c_char,
+ _data: *mut c_void,
+) -> bool {
+ unsafe {
+ luw_mem_writep(
+ ctx,
+ "%s = %s\n\0".as_ptr() as *const c_char,
+ name,
+ value,
+ );
+ }
+
+ return true;
+}
+
+#[no_mangle]
+pub extern "C" fn luw_request_handler(addr: *mut u8) -> i32 {
+ // Need a initalization
+ //
+ // It sucks that rust needs this, this is supposed to be
+ // an opaque structure and the structure is 0-initialised
+ // in luw_init_ctx();
+ let mut ctx_: luw_ctx_t = luw_ctx_t {
+ addr: ptr::null_mut(),
+ mem: ptr::null_mut(),
+ req: ptr::null_mut(),
+ resp: ptr::null_mut(),
+ resp_hdr: ptr::null_mut(),
+ resp_offset: 0,
+ req_buf: ptr::null_mut(),
+ hdrp: ptr::null_mut(),
+ reqp: ptr::null_mut(),
+ };
+ let ctx: *mut luw_ctx_t = &mut ctx_;
+
+ unsafe {
+ // Initialise the context structure.
+ //
+ // addr is the address of the previously allocated memory shared
+ // between the module and unit.
+ //
+ // The response data will be stored @ addr + offset (of 4096 bytes).
+ // This will leave some space for the response headers.
+ luw_init_ctx(ctx, addr, 4096);
+
+ // Allocate memory to store the request and copy the request data.
+ luw_set_req_buf(ctx, &mut REQUEST_BUF, luw_srb_flags_t_LUW_SRB_NONE);
+
+ // Define the Response Body Text.
+
+ luw_mem_writep(
+ ctx,
+ " * Welcome to WebAssembly in Rust on Unit! \
+ [libunit-wasm (%d.%d.%d/%#0.8x)] \
+ *\n\n\0"
+ .as_ptr() as *const c_char,
+ LUW_VERSION_MAJOR,
+ LUW_VERSION_MINOR,
+ LUW_VERSION_PATCH,
+ LUW_VERSION_NUMBER,
+ );
+
+ luw_mem_writep(ctx, "[Request Info]\n\0".as_ptr() as *const c_char);
+
+ luw_mem_writep(
+ ctx,
+ "REQUEST_PATH = %s\n\0".as_ptr() as *const c_char,
+ luw_get_http_path(ctx) as *const c_char,
+ );
+ luw_mem_writep(
+ ctx,
+ "METHOD = %s\n\0".as_ptr() as *const c_char,
+ luw_get_http_method(ctx) as *const c_char,
+ );
+ luw_mem_writep(
+ ctx,
+ "VERSION = %s\n\0".as_ptr() as *const c_char,
+ luw_get_http_version(ctx) as *const c_char,
+ );
+ luw_mem_writep(
+ ctx,
+ "QUERY = %s\n\0".as_ptr() as *const c_char,
+ luw_get_http_query(ctx) as *const c_char,
+ );
+ luw_mem_writep(
+ ctx,
+ "REMOTE = %s\n\0".as_ptr() as *const c_char,
+ luw_get_http_remote(ctx) as *const c_char,
+ );
+ luw_mem_writep(
+ ctx,
+ "LOCAL_ADDR = %s\n\0".as_ptr() as *const c_char,
+ luw_get_http_local_addr(ctx) as *const c_char,
+ );
+ luw_mem_writep(
+ ctx,
+ "LOCAL_PORT = %s\n\0".as_ptr() as *const c_char,
+ luw_get_http_local_port(ctx) as *const c_char,
+ );
+ luw_mem_writep(
+ ctx,
+ "SERVER_NAME = %s\n\0".as_ptr() as *const c_char,
+ luw_get_http_server_name(ctx) as *const c_char,
+ );
+
+ luw_mem_writep(
+ ctx,
+ "\n[Request Headers]\n\0".as_ptr() as *const c_char,
+ );
+
+ luw_http_hdr_iter(ctx, Some(hdr_iter_func), ptr::null_mut());
+
+ let method = CStr::from_ptr(luw_get_http_method(ctx)).to_str().unwrap();
+ if method == "POST" || method == "PUT" {
+ luw_mem_writep(
+ ctx,
+ "\n[%s data]\n\0".as_ptr() as *const c_char,
+ luw_get_http_method(ctx) as *const c_char,
+ );
+ luw_mem_writep_data(
+ ctx,
+ luw_get_http_content(ctx),
+ luw_get_http_content_len(ctx),
+ );
+ luw_mem_writep(ctx, "\n\0".as_ptr() as *const c_char);
+ }
+
+ let content_len = format!("{}\0", luw_get_response_data_size(ctx));
+
+ // Init Response Headers
+ //
+ // Needs the context, number of headers about to add as well as
+ // the offset where to store the headers. In this case we are
+ // storing the response headers at the beginning of our shared
+ // memory at offset 0.
+
+ luw_http_init_headers(ctx, 2, 0);
+ luw_http_add_header(
+ ctx,
+ 0,
+ "Content-Type\0".as_ptr() as *const c_char,
+ "text/plain\0".as_ptr() as *const c_char,
+ );
+ luw_http_add_header(
+ ctx,
+ 1,
+ "Content-Length\0".as_ptr() as *const c_char,
+ content_len.as_ptr() as *const c_char,
+ );
+
+ // This calls nxt_wasm_send_headers() in Unit
+ luw_http_send_headers(ctx);
+
+ // This calls nxt_wasm_send_response() in Unit
+ luw_http_send_response(ctx);
+
+ // This calls nxt_wasm_response_end() in Unit
+ luw_http_response_end();
+ }
+
+ return 0;
+}
diff --git a/examples/rust/upload-reflector/Cargo.toml b/examples/rust/upload-reflector/Cargo.toml
new file mode 100644
index 0000000..dc9d23f
--- /dev/null
+++ b/examples/rust/upload-reflector/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "rust-upload-reflector"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+unit-wasm = { path = "../../../src/rust", version = "0.1.0-beta" }
+
+[lib]
+crate-type = ["cdylib"]
diff --git a/examples/rust/upload-reflector/src/lib.rs b/examples/rust/upload-reflector/src/lib.rs
new file mode 100644
index 0000000..9893d5a
--- /dev/null
+++ b/examples/rust/upload-reflector/src/lib.rs
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * Copyright (C) Andrew Clayton
+ * Copyright (C) Timo Stark
+ * Copyright (C) F5, Inc.
+ */
+
+// Include RAW FFI Bindings.
+// @todo: Replace this with the new native Rust API
+use unit_wasm::ffi::*;
+
+use std::os::raw::c_char;
+use std::os::raw::c_void;
+use std::ptr;
+
+static mut CTX: luw_ctx_t = luw_ctx_t {
+ addr: ptr::null_mut(),
+ mem: ptr::null_mut(),
+ req: ptr::null_mut(),
+ resp: ptr::null_mut(),
+ resp_hdr: ptr::null_mut(),
+ resp_offset: 0,
+ req_buf: ptr::null_mut(),
+ hdrp: ptr::null_mut(),
+ reqp: ptr::null_mut(),
+};
+
+static mut TOTAL_RESPONSE_SENT: usize = 0;
+
+// Buffer of some size to store the copy of the request
+static mut REQUEST_BUF: *mut u8 = ptr::null_mut();
+
+#[no_mangle]
+pub extern "C" fn luw_response_end_handler() {
+ unsafe {
+ TOTAL_RESPONSE_SENT = 0;
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn luw_request_end_handler() {
+ unsafe {
+ if REQUEST_BUF.is_null() {
+ return;
+ }
+
+ luw_free(REQUEST_BUF as *mut c_void);
+ REQUEST_BUF = ptr::null_mut();
+ }
+}
+
+pub fn upload_reflector(ctx: *mut luw_ctx_t) -> i32 {
+ let write_bytes: usize;
+
+ unsafe {
+ // Send headers
+ if TOTAL_RESPONSE_SENT == 0 {
+ let content_len = format!("{}\0", luw_get_http_content_len(ctx));
+ let defct = "application/octet-stream\0".as_ptr() as *const c_char;
+ let mut ct = luw_http_hdr_get_value(
+ ctx,
+ "Content-Type\0".as_ptr() as *const c_char,
+ );
+
+ if ct == ptr::null_mut() {
+ ct = defct;
+ }
+
+ luw_http_init_headers(ctx, 2, 0);
+ luw_http_add_header(
+ ctx,
+ 0,
+ "Content-Type\0".as_ptr() as *const c_char,
+ ct,
+ );
+ luw_http_add_header(
+ ctx,
+ 1,
+ "Content-Length\0".as_ptr() as *const c_char,
+ content_len.as_ptr() as *const c_char,
+ );
+ luw_http_send_headers(ctx);
+ }
+
+ write_bytes = luw_mem_fill_buf_from_req(ctx, TOTAL_RESPONSE_SENT);
+ TOTAL_RESPONSE_SENT += write_bytes;
+
+ luw_http_send_response(ctx);
+
+ if TOTAL_RESPONSE_SENT == luw_get_http_content_len(ctx) {
+ // Tell Unit no more data to send
+ luw_http_response_end();
+ }
+ }
+
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn luw_request_handler(addr: *mut u8) -> i32 {
+ unsafe {
+ let ctx: *mut luw_ctx_t = &mut CTX;
+
+ if REQUEST_BUF.is_null() {
+ luw_init_ctx(ctx, addr, 0 /* Response offset */);
+ /*
+ * Take a copy of the request and use that, we do this
+ * in APPEND mode so we can build up request_buf from
+ * multiple requests.
+ *
+ * Just allocate memory for the total amount of data we
+ * expect to get, this includes the request structure
+ * itself as well as any body content.
+ */
+ luw_set_req_buf(
+ ctx,
+ &mut REQUEST_BUF,
+ luw_srb_flags_t_LUW_SRB_APPEND
+ | luw_srb_flags_t_LUW_SRB_ALLOC
+ | luw_srb_flags_t_LUW_SRB_FULL_SIZE,
+ );
+ } else {
+ luw_req_buf_append(ctx, addr);
+ }
+
+ upload_reflector(ctx);
+ }
+
+ return 0;
+}
diff --git a/shared.mk b/shared.mk
new file mode 100644
index 0000000..5c2e112
--- /dev/null
+++ b/shared.mk
@@ -0,0 +1,51 @@
+# Some common Makefile stuff
+
+# Look for wasi-sysroot in some common places, falling back
+# to provided WASI_SYSROOT
+ifneq ("$(wildcard /usr/wasm32-wasi)", "")
+ # Fedora
+ WASI_SYSROOT ?= /usr/wasm32-wasi
+else ifneq ("$(wildcard /usr/local/share/wasi-sysroot)", "")
+ # FreeBSD
+ WASI_SYSROOT ?= /usr/local/share/wasi-sysroot
+endif
+
+# By default compiler etc output is hidden, use
+# make V=1 ...
+# to show it
+v = @
+ifeq ($V,1)
+ v =
+endif
+
+# Optionally enable debugging builds with
+# make D=1 ...
+# -g is always used, this just changes the optimisation level.
+# On GCC this would be -Og, however according to the clang-16(1)
+# man page, -O0 'generates the most debuggable code'.
+ifeq ($D,1)
+ CFLAGS += -O0
+else
+ CFLAGS += -O2
+endif
+
+# Optionally enable Werror with
+# make E=1 ...
+ifeq ($E,1)
+ CFLAGS += -Werror
+endif
+
+# Pretty print compiler etc actions...
+PP_CC = @echo ' CC '
+PP_AR = @echo ' AR '
+PP_CCLNK = @echo ' CCLNK '
+PP_GEN = @echo ' GEN '
+
+CC = clang
+CFLAGS += -Wall -Wextra -Wdeclaration-after-statement -Wvla \
+ -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition \
+ -Wimplicit-function-declaration -Wimplicit-int -Wint-conversion \
+ -std=gnu11 -g -fno-common -fno-strict-aliasing \
+ --target=wasm32-wasi --sysroot=$(WASI_SYSROOT)
+LDFLAGS = -Wl,--no-entry,--export=__heap_base,--export=__data_end,--export=malloc,--export=free,--stack-first,-z,stack-size=$$((8*1024*1024)) \
+ -mexec-model=reactor --rtlib=compiler-rt
diff --git a/src/c/.gitignore b/src/c/.gitignore
new file mode 100644
index 0000000..a676009
--- /dev/null
+++ b/src/c/.gitignore
@@ -0,0 +1 @@
+libunit-wasm.a
diff --git a/src/c/Makefile b/src/c/Makefile
new file mode 100644
index 0000000..83262f6
--- /dev/null
+++ b/src/c/Makefile
@@ -0,0 +1,21 @@
+include ../../shared.mk
+
+CFLAGS += -Iinclude
+
+SDIR = src/c
+
+TARGETS = libunit-wasm.o libunit-wasm.a
+
+.PHONY: libunit-wasm
+libunit-wasm: $(TARGETS)
+
+libunit-wasm.o: libunit-wasm.c include/unit/unit-wasm.h
+ $(PP_CC) $(SDIR)/$@
+ $(v)$(CC) $(CFLAGS) -fvisibility=hidden -c $<
+
+libunit-wasm.a: libunit-wasm.o
+ $(PP_AR) $(SDIR)/$@
+ $(v)llvm-ar rcs $@ $<
+
+clean:
+ rm -f *.o *.a *.gch
diff --git a/src/c/include/unit/unit-wasm.h b/src/c/include/unit/unit-wasm.h
new file mode 100644
index 0000000..df3852a
--- /dev/null
+++ b/src/c/include/unit/unit-wasm.h
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * Copyright (C) Andrew Clayton
+ * Copyright (C) F5, Inc.
+ */
+
+#ifndef _UNIT_WASM_H_
+#define _UNIT_WASM_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <strings.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LUW_VERSION_MAJOR 0
+#define LUW_VERSION_MINOR 1
+#define LUW_VERSION_PATCH 0
+
+/* Version number in hex 0xMMmmpp00 */
+#define LUW_VERSION_NUMBER \
+ ( (LUW_VERSION_MAJOR << 24) | \
+ (LUW_VERSION_MINOR << 16) | \
+ (LUW_VERSION_PATCH << 8) )
+
+#define __luw_export_name(name) __attribute__((export_name(name)))
+
+#define __luw_unused __attribute__((unused))
+#define __luw_maybe_unused __luw_unused
+
+typedef uint64_t u64;
+typedef int64_t s64;
+typedef uint32_t u32;
+typedef int32_t s32;
+typedef uint16_t u16;
+typedef int16_t s16;
+typedef uint8_t u8;
+typedef int8_t s8;
+
+struct luw_hdr_field {
+ u32 name_off;
+ u32 name_len;
+ u32 value_off;
+ u32 value_len;
+};
+
+struct luw_req {
+ u32 method_off;
+ u32 method_len;
+ u32 version_off;
+ u32 version_len;
+ u32 path_off;
+ u32 path_len;
+ u32 query_off;
+ u32 query_len;
+ u32 remote_off;
+ u32 remote_len;
+ u32 local_addr_off;
+ u32 local_addr_len;
+ u32 local_port_off;
+ u32 local_port_len;
+ u32 server_name_off;
+ u32 server_name_len;
+
+ u32 content_off;
+ u32 content_len;
+ u32 content_sent;
+ u32 total_content_sent;
+
+ u32 request_size;
+
+ u32 nr_fields;
+
+ u32 tls;
+
+ struct luw_hdr_field fields[];
+};
+
+struct luw_resp {
+ u32 size;
+
+ u8 data[];
+};
+
+struct luw_resp_hdr {
+ u32 nr_fields;
+
+ struct luw_hdr_field fields[];
+};
+
+typedef struct {
+ /* pointer to the shared memory */
+ u8 *addr;
+
+ /* points to the end of ctx->resp->data */
+ u8 *mem;
+
+ /* struct luw_req representation of the shared memory */
+ struct luw_req *req;
+
+ /* struct luw_resp representation of the shared memory */
+ struct luw_resp *resp;
+
+ /* struct luw_resp_hdr represnetation of the shared memory */
+ struct luw_resp_hdr *resp_hdr;
+
+ /* offset to where the struct resp starts in the shared memory */
+ size_t resp_offset;
+
+ /* points to the external buffer used for a copy of the request */
+ u8 *req_buf;
+
+ /* points to the end of the fields array in struct luw_resp_hdr */
+ u8 *hdrp;
+
+ /* points to the end of ctx->req_buf */
+ u8 *reqp;
+} luw_ctx_t;
+
+typedef enum {
+ LUW_SRB_NONE = 0x00,
+ LUW_SRB_APPEND = 0x01,
+ LUW_SRB_ALLOC = 0x02,
+ LUW_SRB_FULL_SIZE = 0x04,
+} luw_srb_flags_t;
+#define LUW_SRB_FLAGS_ALL \
+ (LUW_SRB_NONE|LUW_SRB_APPEND|LUW_SRB_ALLOC|LUW_SRB_FULL_SIZE)
+
+typedef struct luw_hdr_field luw_http_hdr_iter_t;
+
+#define luw_foreach_http_hdr(ctx, iter, name, value) \
+ for (iter = ctx.req->fields, \
+ name = (const char *)ctx.req + iter->name_off; \
+ (iter < (ctx.req->fields + ctx.req->nr_fields)) && \
+ (value = (const char *)ctx.req + iter->value_off); \
+ iter++, name = (const char *)ctx.req + iter->name_off)
+
+/* Imported functions from the host/runtime */
+__attribute__((import_module("env"), import_name("nxt_wasm_get_init_mem_size")))
+u32 nxt_wasm_get_init_mem_size(void);
+__attribute__((import_module("env"), import_name("nxt_wasm_response_end")))
+void nxt_wasm_response_end(void);
+__attribute__((import_module("env"), import_name("nxt_wasm_send_headers")))
+void nxt_wasm_send_headers(u32 offset);
+__attribute__((import_module("env"), import_name("nxt_wasm_send_response")))
+void nxt_wasm_send_response(u32 offset);
+
+extern void luw_module_init_handler(void);
+extern void luw_module_end_handler(void);
+extern void luw_request_init_handler(void);
+extern void luw_request_end_handler(void);
+extern void luw_response_end_handler(void);
+extern int luw_request_handler(u8 *addr);
+extern void luw_free_handler(u32 addr);
+extern u32 luw_malloc_handler(size_t size);
+
+#pragma GCC visibility push(default)
+
+extern void luw_init_ctx(luw_ctx_t *ctx, u8 *addr, size_t offset);
+extern void luw_destroy_ctx(const luw_ctx_t *ctx);
+extern int luw_set_req_buf(luw_ctx_t *ctx, u8 **buf, unsigned long flags);
+extern const char *luw_get_http_path(const luw_ctx_t *ctx);
+extern const char *luw_get_http_method(const luw_ctx_t *ctx);
+extern const char *luw_get_http_version(const luw_ctx_t *ctx);
+extern const char *luw_get_http_query(const luw_ctx_t *ctx);
+extern const char *luw_get_http_remote(const luw_ctx_t *ctx);
+extern const char *luw_get_http_local_addr(const luw_ctx_t *ctx);
+extern const char *luw_get_http_local_port(const luw_ctx_t *ctx);
+extern const char *luw_get_http_server_name(const luw_ctx_t *ctx);
+extern const u8 *luw_get_http_content(const luw_ctx_t *ctx);
+extern size_t luw_get_http_content_len(const luw_ctx_t *ctx);
+extern size_t luw_get_http_content_sent(const luw_ctx_t *ctx);
+extern bool luw_http_is_tls(const luw_ctx_t *ctx);
+extern void luw_http_hdr_iter(luw_ctx_t *ctx,
+ bool (*luw_http_hdr_iter_func)(luw_ctx_t *ctx,
+ const char *name,
+ const char *value,
+ void *data),
+ void *user_data);
+extern const char *luw_http_hdr_get_value(luw_ctx_t *ctx, const char *hdr);
+extern size_t luw_get_response_data_size(const luw_ctx_t *ctx);
+extern int luw_mem_writep(luw_ctx_t *ctx, const char *fmt, ...);
+extern size_t luw_mem_writep_data(luw_ctx_t *ctx, const u8 *src, size_t size);
+extern void luw_req_buf_append(luw_ctx_t *ctx, const u8 *src);
+extern size_t luw_mem_fill_buf_from_req(luw_ctx_t *ctx, size_t from);
+extern void luw_mem_reset(luw_ctx_t *ctx);
+extern void luw_http_send_response(const luw_ctx_t *ctx);
+extern void luw_http_init_headers(luw_ctx_t *ctx, size_t nr, size_t offset);
+extern void luw_http_add_header(luw_ctx_t *ctx, u16 idx, const char *name,
+ const char *value);
+extern void luw_http_send_headers(const luw_ctx_t *ctx);
+extern void luw_http_response_end(void);
+extern u32 luw_mem_get_init_size(void);
+
+/*
+ * Convenience wrappers for the Rust bindings, not for general consumption.
+ */
+extern void *luw_malloc(size_t size);
+extern void luw_free(void *ptr);
+
+#pragma GCC visibility pop
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* _UNIT_WASM_H_ */
diff --git a/src/c/libunit-wasm.c b/src/c/libunit-wasm.c
new file mode 100644
index 0000000..1860fe3
--- /dev/null
+++ b/src/c/libunit-wasm.c
@@ -0,0 +1,381 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * Copyright (C) Andrew Clayton
+ * Copyright (C) F5, Inc.
+ */
+
+#define _GNU_SOURCE
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "unit/unit-wasm.h"
+
+/*
+ * Some handlers are required some are optional.
+ *
+ * They are defined as _weak_ symbols so they can be overridden
+ * in the module.
+ */
+
+/* Optional Handlers */
+__attribute__((export_name("luw_module_init_handler"), __weak__))
+void luw_module_init_handler(void)
+{
+}
+
+__attribute__((export_name("luw_module_end_handler"), __weak__))
+void luw_module_end_handler(void)
+{
+}
+
+__attribute__((export_name("luw_request_init_handler"), __weak__))
+void luw_request_init_handler(void)
+{
+}
+
+__attribute__((export_name("luw_request_end_handler"), __weak__))
+void luw_request_end_handler(void)
+{
+}
+
+__attribute__((export_name("luw_response_end_handler"), __weak__))
+void luw_response_end_handler(void)
+{
+}
+
+/* Required Handlers */
+__attribute__((export_name("luw_request_handler"), __weak__))
+int luw_request_handler(u8 *addr)
+{
+ (void)addr;
+
+ return 0;
+}
+
+__attribute__((export_name("luw_free_handler"), __weak__))
+void luw_free_handler(u32 addr)
+{
+ free((void *)addr);
+}
+
+__attribute__((export_name("luw_malloc_handler"), __weak__))
+u32 luw_malloc_handler(size_t size)
+{
+ return (u32)malloc(size);
+}
+
+void luw_init_ctx(luw_ctx_t *ctx, u8 *addr, size_t offset)
+{
+ *ctx = (luw_ctx_t){ };
+
+ ctx->addr = addr;
+ ctx->req = (struct luw_req *)addr;
+ ctx->resp = (struct luw_resp *)(addr + offset);
+ ctx->mem = ctx->resp->data;
+ ctx->resp_offset = offset;
+ ctx->resp->size = 0;
+ ctx->resp_hdr->nr_fields = 0;
+}
+
+/*
+ * Allows to set an external buffer to be used as a copy for
+ * the request.
+ *
+ * The flags dictate a few behaviours
+ *
+ * LUW_SRB_NONE - No specific action to be performed. It will
+ * simply copy the request data into the specified
+ * buffer.
+ *
+ * LUW_SRB_APPEND - Sets up append mode whereby multiple successive
+ * requests will be appended to the specified buffer.
+ *
+ * The first request will have all its metadata
+ * copied. Subsequent requests will _only_ have the
+ * actual body data appended.
+ *
+ * LUW_SRB_ALLOC - Allocate memory for the specified buffer.
+ *
+ * LUW_SRB_FULL_SIZE - Used in conjunction with LUW_SRB_ALLOC. By
+ * default only ctx->req->request_size is
+ * allocated. If this flag is present it says to
+ * allocate memory for the _entire_ request that
+ * will eventually be sent.
+ */
+int luw_set_req_buf(luw_ctx_t *ctx, u8 **buf, unsigned long flags)
+{
+ size_t alloc_size;
+ size_t copy_bytes;
+
+ /* Check for unknown flags */
+ if (flags & ~LUW_SRB_FLAGS_ALL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Check for invalid combinations of flags */
+ if (flags & LUW_SRB_FULL_SIZE && !(flags & LUW_SRB_ALLOC)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ alloc_size = copy_bytes = ctx->req->request_size;
+
+ if (flags & LUW_SRB_FULL_SIZE)
+ alloc_size = ctx->req->content_off + ctx->req->content_len;
+
+ if (flags & LUW_SRB_ALLOC) {
+ *buf = malloc(alloc_size);
+ if (!*buf)
+ return -1;
+ }
+
+ memcpy(*buf, ctx->addr, copy_bytes);
+ ctx->req_buf = *buf;
+ ctx->req = (struct luw_req *)ctx->req_buf;
+ ctx->reqp = ctx->req_buf;
+
+ if (flags & LUW_SRB_APPEND)
+ ctx->reqp = ctx->req_buf + copy_bytes;
+
+ return 0;
+}
+
+const char *luw_get_http_path(const luw_ctx_t *ctx)
+{
+ return (const char *)ctx->req + ctx->req->path_off;
+}
+
+const char *luw_get_http_method(const luw_ctx_t *ctx)
+{
+ return (const char *)ctx->req + ctx->req->method_off;
+}
+
+const char *luw_get_http_version(const luw_ctx_t *ctx)
+{
+ return (const char *)ctx->req + ctx->req->version_off;
+}
+
+const char *luw_get_http_query(const luw_ctx_t *ctx)
+{
+ return (const char *)ctx->req + ctx->req->query_off;
+}
+
+const char *luw_get_http_remote(const luw_ctx_t *ctx)
+{
+ return (const char *)ctx->req + ctx->req->remote_off;
+}
+
+const char *luw_get_http_local_addr(const luw_ctx_t *ctx)
+{
+ return (const char *)ctx->req + ctx->req->local_addr_off;
+}
+
+const char *luw_get_http_local_port(const luw_ctx_t *ctx)
+{
+ return (const char *)ctx->req + ctx->req->local_port_off;
+}
+
+const char *luw_get_http_server_name(const luw_ctx_t *ctx)
+{
+ return (const char *)ctx->req + ctx->req->server_name_off;
+}
+
+const u8 *luw_get_http_content(const luw_ctx_t *ctx)
+{
+ return (u8 *)ctx->req + ctx->req->content_off;
+}
+
+/* Returns the size of the overall content length */
+size_t luw_get_http_content_len(const luw_ctx_t *ctx)
+{
+ return ctx->req->content_len;
+}
+
+/* Returns the size of the content sent in _this_ request */
+size_t luw_get_http_content_sent(const luw_ctx_t *ctx)
+{
+ return ctx->req->content_sent;
+}
+
+bool luw_http_is_tls(const luw_ctx_t *ctx)
+{
+ return ctx->req->tls;
+}
+
+void luw_http_hdr_iter(luw_ctx_t *ctx,
+ bool (*luw_http_hdr_iter_func)(luw_ctx_t *ctx,
+ const char *name,
+ const char *value,
+ void *data),
+ void *user_data)
+{
+ struct luw_hdr_field *hf;
+ struct luw_hdr_field *hf_end;
+
+ hf_end = ctx->req->fields + ctx->req->nr_fields;
+ for (hf = ctx->req->fields; hf < hf_end; hf++) {
+ const char *name = (const char *)ctx->req + hf->name_off;
+ const char *value = (const char *)ctx->req + hf->value_off;
+ bool again;
+
+ again = luw_http_hdr_iter_func(ctx, name, value, user_data);
+ if (!again)
+ break;
+ }
+}
+
+const char *luw_http_hdr_get_value(luw_ctx_t *ctx, const char *hdr)
+{
+ luw_http_hdr_iter_t *iter;
+ const char *name;
+ const char *value;
+
+ luw_foreach_http_hdr((*ctx), iter, name, value) {
+ if (strcasecmp(name, hdr) == 0)
+ return value;
+ }
+
+ return NULL;
+}
+
+/* Returns the size of ctx->resp->data[] */
+size_t luw_get_response_data_size(const luw_ctx_t *ctx)
+{
+ return ctx->mem - ctx->resp->data;
+}
+
+/* Appends (non-nul terminmates) formatted data to the response buffer */
+__attribute__((__format__(printf, 2, 3)))
+int luw_mem_writep(luw_ctx_t *ctx, const char *fmt, ...)
+{
+ int len;
+ char *logbuf;
+ va_list ap;
+
+ va_start(ap, fmt);
+ len = vasprintf(&logbuf, fmt, ap);
+ if (len == -1) {
+ va_end(ap);
+ return -1;
+ }
+ va_end(ap);
+
+ ctx->mem = mempcpy(ctx->mem, logbuf, len);
+ ctx->resp->size += len;
+
+ free(logbuf);
+
+ return len;
+}
+
+/* Appends data of length size to the response buffer */
+size_t luw_mem_writep_data(luw_ctx_t *ctx, const u8 *src, size_t size)
+{
+ ctx->mem = mempcpy(ctx->mem, src, size);
+ ctx->resp->size += size;
+
+ return ctx->resp->size;
+}
+
+/* Append data from the request to the previously setup request_buffer. */
+void luw_req_buf_append(luw_ctx_t *ctx, const u8 *src)
+{
+ struct luw_req *req = (struct luw_req *)src;
+
+ ctx->reqp = mempcpy(ctx->reqp, src + req->content_off,
+ req->request_size);
+ ctx->req->content_sent = req->content_sent;
+ ctx->req->total_content_sent = req->total_content_sent;
+}
+
+/*
+ * Convenience function to fill the response buffer with data from
+ * the request buffer.
+ *
+ * The runtime allocates NXT_WASM_MEM_SIZE + NXT_WASM_PAGE_SIZE
+ * bytes so we don't need to worry about the size of the actual
+ * response structures.
+ */
+size_t luw_mem_fill_buf_from_req(luw_ctx_t *ctx, size_t from)
+{
+ size_t write_bytes;
+ size_t mem_size = nxt_wasm_get_init_mem_size();
+
+ write_bytes = ctx->req->content_sent;
+ if (write_bytes > mem_size)
+ write_bytes = mem_size;
+
+ memcpy(ctx->resp->data, ctx->req_buf + ctx->req->content_off + from,
+ write_bytes);
+ ctx->resp->size = write_bytes;
+
+ return write_bytes;
+}
+
+void luw_mem_reset(luw_ctx_t *ctx)
+{
+ ctx->mem = ctx->resp->data;
+ ctx->resp->size = 0;
+ ctx->resp_hdr->nr_fields = 0;
+}
+
+void luw_http_send_response(const luw_ctx_t *ctx)
+{
+ nxt_wasm_send_response(ctx->resp_offset);
+}
+
+void luw_http_init_headers(luw_ctx_t *ctx, size_t nr, size_t offset)
+{
+ ctx->resp_hdr = (struct luw_resp_hdr *)(ctx->addr + offset);
+ ctx->hdrp = (u8 *)ctx->resp_hdr + sizeof(struct luw_resp_hdr) +
+ (nr * sizeof(struct luw_hdr_field));
+
+ ctx->resp_hdr->nr_fields = nr;
+}
+
+void luw_http_add_header(luw_ctx_t *ctx, u16 idx, const char *name,
+ const char *value)
+{
+ ctx->resp_hdr->fields[idx].name_off = ctx->hdrp - ctx->addr;
+ ctx->resp_hdr->fields[idx].name_len = strlen(name);
+ ctx->hdrp = mempcpy(ctx->hdrp, name, strlen(name));
+
+ ctx->resp_hdr->fields[idx].value_off = ctx->hdrp - ctx->addr;
+ ctx->resp_hdr->fields[idx].value_len = strlen(value);
+ ctx->hdrp = mempcpy(ctx->hdrp, value, strlen(value));
+}
+
+void luw_http_send_headers(const luw_ctx_t *ctx)
+{
+ nxt_wasm_send_headers((u8 *)ctx->resp_hdr - ctx->addr);
+}
+
+void luw_http_response_end(void)
+{
+ nxt_wasm_response_end();
+}
+
+u32 luw_mem_get_init_size(void)
+{
+ return nxt_wasm_get_init_mem_size();
+}
+
+/* These are really just convenience wrappers for the rust bindings... */
+
+void *luw_malloc(size_t size)
+{
+ return malloc(size);
+}
+
+void luw_free(void *ptr)
+{
+ free(ptr);
+}
diff --git a/src/rust/.gitignore b/src/rust/.gitignore
new file mode 100644
index 0000000..6c481e2
--- /dev/null
+++ b/src/rust/.gitignore
@@ -0,0 +1,4 @@
+bindings.rs
+Cargo.lock
+
+target/
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
new file mode 100644
index 0000000..2e0bba2
--- /dev/null
+++ b/src/rust/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "unit-wasm"
+version = "0.1.0"
+authors = ["Timo Stark <t.stark@f5.com>", "Andrew Clayton <a.clayton@f5.com>"]
+description = "WASM SDK for NGINX Unit"
+license = "Apache-2.0"
+keywords = ["nginx", "unit", "wasm", "wasi"]
+
+[lib]
+crate-type = ["rlib"]
+path = "src/lib.rs"
+
+
+[dependencies]
+unit-wasm-sys = { path = "unit-wasm-sys", version = "0.1.1" }
diff --git a/src/rust/Makefile b/src/rust/Makefile
new file mode 100644
index 0000000..d2e705a
--- /dev/null
+++ b/src/rust/Makefile
@@ -0,0 +1,11 @@
+include ../../shared.mk
+
+SDIR = src/rust
+
+rustlib:
+ $(PP_GEN) $(SDIR)/target/wasm32-wasi
+ $(v)cargo build --target=wasm32-wasi
+
+clean:
+ rm -f Cargo.lock unit-wasm-sys/Cargo.lock
+ rm -rf target/ unit-wasm-sys/target/
diff --git a/src/rust/README.md b/src/rust/README.md
new file mode 100644
index 0000000..a48e2cd
--- /dev/null
+++ b/src/rust/README.md
@@ -0,0 +1,18 @@
+# Quickstart in Developing Rust WebAssembly Modules for Unit
+
+The current version is published to crates.io. To get started with the SDK
+include `unit-wasm` as dependency.
+
+```
+cargo add unit-wasm
+```
+
+## Prerequisites
+
+- target add wasm32-wasi. `rustup target add wasm32-wasi`
+
+## From Source
+
+The Rust implementation is in an early stage. If you would like to build the
+crate by yourself, we have to generate the `libunit-wasm` first. This step is
+NOT included in the build process.
diff --git a/src/rust/src/ffi/mod.rs b/src/rust/src/ffi/mod.rs
new file mode 100644
index 0000000..087148a
--- /dev/null
+++ b/src/rust/src/ffi/mod.rs
@@ -0,0 +1,3 @@
+// @todo: Check if this is valid?!
+extern crate unit_wasm_sys;
+pub use self::unit_wasm_sys::*;
diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs
new file mode 100644
index 0000000..dface32
--- /dev/null
+++ b/src/rust/src/lib.rs
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * Copyright (C) Timo Stark
+ * Copyright (C) F5, Inc.
+ */
+
+pub mod ffi;
diff --git a/src/rust/unit-wasm-sys/Cargo.toml b/src/rust/unit-wasm-sys/Cargo.toml
new file mode 100644
index 0000000..d8f64a3
--- /dev/null
+++ b/src/rust/unit-wasm-sys/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "unit-wasm-sys"
+version = "0.1.1"
+edition = "2021"
+authors = ["Timo Stark <t.stark@nginx.com>", "Andrew Clayton <a.clayton@nginx.com>"]
+links = "unit-wasm"
+description = "Native bindings for the libunit-wasm C API"
+license = "Apache-2.0"
+keywords = ["nginx", "unit", "ffi", "sys"]
+
+[lib]
+name = "unit_wasm_sys"
+path = "lib.rs"
+crate-type = ["staticlib", "rlib"]
+
+
+[build-dependencies]
+bindgen = "0.66.1"
diff --git a/src/rust/unit-wasm-sys/README.md b/src/rust/unit-wasm-sys/README.md
new file mode 100644
index 0000000..fbc53f0
--- /dev/null
+++ b/src/rust/unit-wasm-sys/README.md
@@ -0,0 +1,4 @@
+# unit-wasm sys crate
+
+The symbolic link is beeing used to share the c code with cargo during
+buildtime.
diff --git a/src/rust/unit-wasm-sys/build.rs b/src/rust/unit-wasm-sys/build.rs
new file mode 100644
index 0000000..bd2ebd5
--- /dev/null
+++ b/src/rust/unit-wasm-sys/build.rs
@@ -0,0 +1,52 @@
+// buildscript for the unit-wasm-sys crate.
+
+use std::env;
+use std::path::{Path, PathBuf};
+
+fn main() {
+ // Tell rustc where to find the libunit-wasm library.
+ let libunit_wasm_dir = "libunit-wasm";
+
+ let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+
+ // Some generics
+ println!("cargo:rerun-if-changed=build.rs");
+ println!("cargo:rerun-if-changed=wrapper.h");
+
+ // The rustc-link-search tells Cargo to pass the `-L` flag to the
+ // compiler to add a directory to the library search plugin. The
+ // `native` keyword means "only looking for `native libraries` in
+ // this directory".
+ println!(
+ "cargo:rustc-link-search=native={}",
+ Path::new(&dir).join(libunit_wasm_dir).display()
+ );
+
+ // The rustc-link-lib tells Cargo to link the given library using
+ // the compiler's `-l` flag. This is needed to start building our
+ // FFIs.
+ println!("cargo:rustc-link-lib=static=unit-wasm");
+
+ generate_bindings();
+}
+
+fn generate_bindings() {
+ let wasi_sysroot = "--sysroot=".to_owned() + &env::var("WASI_SYSROOT").unwrap();
+ let bindings = bindgen::Builder::default()
+ // The input header file.
+ .header("libunit-wasm/include/unit/unit-wasm.h")
+ .allowlist_function("^luw_.*")
+ .allowlist_var("^luw_.*")
+ .allowlist_type("^luw_.*")
+ .clang_args(vec![wasi_sysroot]) // Needed for strings.h
+ .generate()
+ .expect("Unable to generate bindings");
+
+ let out_dir_env =
+ env::var("OUT_DIR").expect("The required environment variable OUT_DIR was not set");
+ let out_path = PathBuf::from(out_dir_env);
+
+ bindings
+ .write_to_file(out_path.join("bindings.rs"))
+ .expect("Couldn't write bindings!");
+}
diff --git a/src/rust/unit-wasm-sys/lib.rs b/src/rust/unit-wasm-sys/lib.rs
new file mode 100644
index 0000000..9ef60f6
--- /dev/null
+++ b/src/rust/unit-wasm-sys/lib.rs
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * Copyright (C) Timo Stark
+ * Copyright (C) F5, Inc.
+ */
+
+#[doc(hidden)]
+mod bindings {
+ #![allow(non_upper_case_globals)]
+ #![allow(non_camel_case_types)]
+ #![allow(dead_code)]
+
+ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+ include!("macros.rs");
+}
+
+#[doc(no_inline)]
+pub use bindings::*;
diff --git a/src/rust/unit-wasm-sys/libunit-wasm b/src/rust/unit-wasm-sys/libunit-wasm
new file mode 120000
index 0000000..2fcc511
--- /dev/null
+++ b/src/rust/unit-wasm-sys/libunit-wasm
@@ -0,0 +1 @@
+../../c \ No newline at end of file
diff --git a/src/rust/unit-wasm-sys/macros.rs b/src/rust/unit-wasm-sys/macros.rs
new file mode 100644
index 0000000..d7fde22
--- /dev/null
+++ b/src/rust/unit-wasm-sys/macros.rs
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/*
+ * Copyright (C) Andrew Clayton
+ * Copyright (C) F5, Inc.
+ */
+
+pub const LUW_VERSION_MAJOR: i32 = 0;
+pub const LUW_VERSION_MINOR: i32 = 1;
+pub const LUW_VERSION_PATCH: i32 = 0;
+
+pub const LUW_VERSION_NUMBER: i32 =
+ (LUW_VERSION_MAJOR << 24) |
+ (LUW_VERSION_MINOR << 16) |
+ (LUW_VERSION_PATCH << 8);
diff --git a/unit-wasm-conf.json b/unit-wasm-conf.json
new file mode 100644
index 0000000..fe997b3
--- /dev/null
+++ b/unit-wasm-conf.json
@@ -0,0 +1,87 @@
+{
+ "listeners": {
+ "[::1]:8888": {
+ "pass": "routes"
+ }
+ },
+
+ "settings": {
+ "http": {
+ "max_body_size": 1073741824
+ }
+ },
+
+ "routes": [
+ {
+ "match": {
+ "uri": "/echo*"
+ },
+ "action": {
+ "pass": "applications/luw-echo-request"
+ }
+ },
+ {
+ "match": {
+ "uri": "/upload*"
+ },
+ "action": {
+ "pass": "applications/luw-upload-reflector"
+ }
+ },
+ {
+ "match": {
+ "uri": "/rust-echo*"
+ },
+ "action": {
+ "pass": "applications/rust-echo-test"
+ }
+ },
+ {
+ "match": {
+ "uri": "/rust-upload*"
+ },
+ "action": {
+ "pass": "applications/rust-upload-reflector"
+ }
+ }
+ ],
+
+ "applications": {
+ "luw-echo-request": {
+ "type": "wasm",
+ "module": "/path/to/unit-wasm/examples/c/luw-echo-request.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "module_init_handler": "luw_module_init_handler",
+ "module_end_handler": "luw_module_end_handler"
+ },
+ "luw-upload-reflector": {
+ "type": "wasm",
+ "module": "/path/to/unit-wasm/examples/c/luw-upload-reflector.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "request_end_handler": "luw_request_end_handler",
+ "response_end_handler": "luw_response_end_handler"
+ },
+ "rust-echo-test": {
+ "type": "wasm",
+ "module": "/path/to/unit-wasm/examples/rust/echo-request/target/wasm32-wasi/debug/rust_echo_test.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "module_init_handler": "luw_module_init_handler",
+ "module_end_handler": "luw_module_end_handler"
+ },
+ "rust-upload-reflector": {
+ "type": "wasm",
+ "module": "/path/to/unit-wasm/examples/rust/upload-reflector/rust_upload_reflector.wasm",
+ "request_handler": "luw_request_handler",
+ "malloc_handler": "luw_malloc_handler",
+ "free_handler": "luw_free_handler",
+ "request_end_handler": "luw_request_end_handler",
+ "response_end_handler": "luw_response_end_handler"
+ }
+ }
+}