summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorAndrew Clayton <a.clayton@nginx.com>2023-08-02 17:03:48 +0100
committerAndrew Clayton <a.clayton@nginx.com>2023-08-21 23:24:12 +0100
commitd6ed6a219b31a58526721f96195c80061d41ce54 (patch)
tree17a1fd6ecf72a327916ff0f8bc7aaf85b981ceff /src
downloadunit-wasm-0.1.0.tar.gz
unit-wasm-0.1.0.tar.bz2
Initial commitv0.1.0
libunit-wasm and example C and Rust WebAssembly modules for NGINX Unit. Co-developed-by: Timo Stark <t.stark@nginx.com> Co-developed-by: Liam Crilly <liam@nginx.com> Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
Diffstat (limited to 'src')
-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
16 files changed, 782 insertions, 0 deletions
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);