From 90bda056b4e7cc1f4bbf0a2f82e6c97e9dfac74e Mon Sep 17 00:00:00 2001 From: Torin Sandall Date: Sat, 6 Oct 2018 18:29:31 -0700 Subject: [PATCH] Add C library implementing operations for WASM These changes add a C library that implements low-level data operations and JSON parsing for WASM policies. The output of the WASM build process are checked into the repository so that OPA can easily access the bytecode for test and other purposes. Signed-off-by: Torin Sandall --- .gitignore | 1 + Makefile | 45 +- internal/cmd/genopawasm/main.go | 135 ++++++ internal/compiler/wasm/opa/opa.go | 25 + internal/compiler/wasm/opa/opa.wasm | Bin 0 -> 12053 bytes main.go | 1 + wasm/Dockerfile | 33 ++ wasm/Makefile | 79 ++++ wasm/README.md | 42 ++ wasm/src/json.c | 497 ++++++++++++++++++++ wasm/src/json.h | 34 ++ wasm/src/malloc.c | 20 + wasm/src/malloc.h | 9 + wasm/src/std.h | 19 + wasm/src/string.c | 90 ++++ wasm/src/string.h | 14 + wasm/src/undefined.symbols | 2 + wasm/src/value.c | 689 ++++++++++++++++++++++++++++ wasm/src/value.h | 115 +++++ wasm/test.js | 113 +++++ wasm/tests/test.c | 493 ++++++++++++++++++++ wasm/tests/undefined.symbols | 4 + 22 files changed, 2456 insertions(+), 4 deletions(-) create mode 100644 internal/cmd/genopawasm/main.go create mode 100644 internal/compiler/wasm/opa/opa.go create mode 100755 internal/compiler/wasm/opa/opa.wasm create mode 100644 wasm/Dockerfile create mode 100644 wasm/Makefile create mode 100644 wasm/README.md create mode 100644 wasm/src/json.c create mode 100644 wasm/src/json.h create mode 100644 wasm/src/malloc.c create mode 100644 wasm/src/malloc.h create mode 100644 wasm/src/std.h create mode 100644 wasm/src/string.c create mode 100644 wasm/src/string.h create mode 100644 wasm/src/undefined.symbols create mode 100644 wasm/src/value.c create mode 100644 wasm/src/value.h create mode 100644 wasm/test.js create mode 100644 wasm/tests/test.c create mode 100644 wasm/tests/undefined.symbols diff --git a/.gitignore b/.gitignore index cff66bae19..9d22d7df90 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ coverage opa_* .Dockerfile_* _release +wasm/_obj site.tar.gz policy.wasm diff --git a/Makefile b/Makefile index 9e982bca06..2b44e2a23c 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,9 @@ GOVERSION := 1.10 GOARCH := $(shell go env GOARCH) GOOS := $(shell go env GOOS) +DOCKER_INSTALLED := $(shell hash docker 2>/dev/null && echo 1 || echo 0) +DOCKER := docker + BIN := opa_$(GOOS)_$(GOARCH) REPOSITORY := openpolicyagent @@ -45,12 +48,24 @@ version: deps: @./build/install-deps.sh +.PHONY: wasm-build +wasm-build: +ifeq ($(DOCKER_INSTALLED), 1) + @$(MAKE) -C wasm build + cp wasm/_obj/opa.wasm internal/compiler/wasm/opa/opa.wasm +else + @echo "Docker not installed. Skipping OPA-WASM library build." +endif + .PHONY: generate -generate: +generate: wasm-build $(GO) generate .PHONY: build -build: generate +build: go-build + +.PHONY: go-build +go-build: generate $(GO) build -o $(BIN) -ldflags $(LDFLAGS) .PHONY: image @@ -95,9 +110,27 @@ install: generate $(GO) install -ldflags $(LDFLAGS) .PHONY: test -test: generate +test: opa-wasm-test go-test wasm-test + +.PHONY: opa-wasm-test +opa-wasm-test: +ifeq ($(DOCKER_INSTALLED), 1) + @$(MAKE) -C wasm test +else + @echo "Docker not installed. Skipping OPA-WASM library test." +endif + +.PHONY: go-test +go-test: generate $(GO) test ./... +.PHONY: wasm-test +wasm-test: +ifeq ($(DOCKER_INSTALLED), 1) +else + @echo "Docker not installed. Skipping WASM-based test execution." +endif + .PHONY: perf perf: generate $(GO) test -run=- -bench=. -benchmem ./... @@ -121,8 +154,12 @@ check-lint: fmt: $(GO) fmt ./... +.PHONY: wasm-clean +wasm-clean: + @$(MAKE) -C wasm clean + .PHONY: clean -clean: +clean: wasm-clean rm -f .Dockerfile_* rm -f opa_*_* rm -fr site.tar.gz docs/_site docs/node_modules docs/book/_book docs/book/node_modules diff --git a/internal/cmd/genopawasm/main.go b/internal/cmd/genopawasm/main.go new file mode 100644 index 0000000000..d9702288a0 --- /dev/null +++ b/internal/cmd/genopawasm/main.go @@ -0,0 +1,135 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "os" + "path" + + "github.com/spf13/cobra" +) + +type params struct { + Output string +} + +func main() { + + var params params + executable := path.Base(os.Args[0]) + + command := &cobra.Command{ + Use: executable, + Short: executable + " ", + RunE: func(_ *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("provide path of opa.wasm file") + } + return run(params, args) + }, + } + + command.Flags().StringVarP(¶ms.Output, "output", "o", "", "set path of output file (default: stdout)") + + if err := command.Execute(); err != nil { + os.Exit(1) + } +} + +func run(params params, args []string) error { + + var out io.Writer + + if params.Output != "" { + f, err := os.Create(params.Output) + if err != nil { + return err + } + defer f.Close() + out = f + } else { + out = os.Stdout + } + + _, err := out.Write([]byte(`// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// THIS FILE IS GENERATED. DO NOT EDIT. + +// Package opa contains bytecode for the OPA-WASM library. +package opa + +import ( + "bytes" + "compress/gzip" + "io/ioutil" +) + +// Bytes returns the OPA-WASM bytecode. +func Bytes() ([]byte, error) { + gr, err := gzip.NewReader(bytes.NewBuffer(gzipped)) + if err != nil { + return nil, err + } + return ioutil.ReadAll(gr) +} + +`)) + + if err != nil { + return err + } + + _, err = out.Write([]byte(`var gzipped = []byte("`)) + if err != nil { + return err + } + + in, err := os.Open(args[0]) + if err != nil { + return err + } + + defer in.Close() + + var buf bytes.Buffer + gw := gzip.NewWriter(&buf) + _, err = io.Copy(gw, in) + if err != nil { + return err + } + + gw.Close() + + for _, b := range buf.Bytes() { + if _, err := out.Write([]byte(`\x`)); err != nil { + return err + } + if _, err := out.Write(asciihex(b)); err != nil { + return err + } + } + + _, err = out.Write([]byte(`")`)) + if err != nil { + return err + } + + _, err = out.Write([]byte("\n")) + return err +} + +var digits = "0123456789ABCDEF" + +func asciihex(b byte) []byte { + lo := digits[(b & 0x0F)] + hi := digits[(b >> 4)] + return []byte{hi, lo} +} diff --git a/internal/compiler/wasm/opa/opa.go b/internal/compiler/wasm/opa/opa.go new file mode 100644 index 0000000000..8118cfca5a --- /dev/null +++ b/internal/compiler/wasm/opa/opa.go @@ -0,0 +1,25 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// THIS FILE IS GENERATED. DO NOT EDIT. + +// Package opa contains bytecode for the OPA-WASM library. +package opa + +import ( + "bytes" + "compress/gzip" + "io/ioutil" +) + +// Bytes returns the OPA-WASM bytecode. +func Bytes() ([]byte, error) { + gr, err := gzip.NewReader(bytes.NewBuffer(gzipped)) + if err != nil { + return nil, err + } + return ioutil.ReadAll(gr) +} + +var gzipped = []byte("\x1F\x8B\x08\x00\x00\x00\x00\x00\x00\xFF\xCC\x7A\x4D\x8C\x5C\xC7\x71\x7F\x55\x77\xBF\x37\x6F\xA6\xE7\xED\x0E\x25\x51\xA2\xB5\xFA\xA8\xF7\x24\xD9\xA4\x49\xCA\xB2\x65\xAD\x2C\xCB\xB6\xB6\xD7\xDC\x25\xD7\x34\xFE\x16\x20\xFD\x13\x84\x48\x34\x1C\x71\x87\xE4\xCE\x7E\x51\xBB\x43\x7D\x00\x8B\x9D\x45\x0C\x23\x0A\x62\x24\x07\x23\x81\x9D\x43\x2C\xD3\x40\x94\x1C\x98\x20\x31\x02\x41\x49\x00\x01\x31\x72\xCE\x21\xC7\x04\x48\x62\x40\xB9\xE4\x92\x4B\x8E\x89\x83\xAA\xEE\xF7\x31\x1F\xAB\x8F\x5B\x76\x81\x79\xF3\xFA\xA3\xBA\xEA\x57\xD5\x55\xD5\x3D\x05\xBD\xFD\x6D\x04\x00\xFC\x8D\x13\x57\xD5\x68\x84\xA3\xAB\x38\x82\xAB\x00\x57\x91\xBF\xAB\xD1\x21\x7F\x1E\x1C\x48\x3B\x1E\xF2\xC7\xC1\x55\xE0\x37\xEE\xC0\x03\x19\x33\x82\xAB\x7A\x54\x7C\xE2\xE8\xAA\x19\xF9\x27\xD3\x3B\x94\xCF\x03\xF5\xA8\xD2\xFD\x9D\xD7\xE3\xED\xFE\xF6\xEE\xDE\x5B\x0A\xE4\xAD\xB9\x7B\xAB\xD7\xED\xBD\xBA\xBB\x37\x04\xD4\x2B\x17\x94\x66\x46\x0C\xF0\x9F\xD6\x5A\x47\xF2\x05\x01\x51\xC7\x0D\x9D\xE8\x66\x0B\x34\x26\x3A\xB1\x16\xA0\xAD\x53\x19\xA3\xF5\x1C\xF2\x67\x7B\xBE\x03\x1A\x34\x98\x08\x6F\x21\x62\xBC\x60\x46\xE8\xDE\x79\xDB\xD8\x11\x14\x8F\xA3\x84\x3F\xFF\x28\xB1\x8D\xFF\x68\xAD\x9E\xE8\x76\xDF\xE8\xED\x6F\x77\xAF\xF5\xB6\xB6\xBA\xD7\x86\xBB\x7B\xFB\x80\xB6\xDB\xBD\xD9\xEF\xDD\xEA\xBE\xDA\xDB\xEF\x6B\x6C\x77\xBB\xEB\xFB\xBB\xDD\x9B\xBD\x9D\xF5\xAD\xBE\x56\xAD\x6E\x77\xBD\x37\xEC\x75\xFB\x3B\xEB\x5A\xB7\x98\xF1\xED\xDE\xD6\xD6\xEE\x35\x50\x09\xBF\x5C\xDF\xEB\xF7\x41\xDF\xC7\x5F\x5F\xEF\x6D\xDD\xEE\x77\x6F\xF4\x87\xDD\xDD\x57\x07\xFD\x6B\x43\x30\x27\xAA\xE6\x6B\xBB\xDB\xB7\x7A\x7B\x7D\x88\xE6\xB8\xCD\x0F\xE0\xB1\x10\x3F\x38\x3E\xB7\xB7\xB7\xD7\x7B\xAB\xBB\xD3\x1B\x6E\xBC\xDE\x87\xC6\xBD\x33\x3A\x21\x49\xC7\x5A\xA1\x79\xB2\x7A\xDF\x18\xF6\xF7\x8A\xF5\x5B\xF7\x4D\xB4\xFB\xE9\x76\x6E\xBC\x19\xDA\x0F\x54\x0D\x5B\xFD\x9D\x1B\xC3\x9B\x05\x85\xF4\xFE\xA9\x1E\x4F\x63\x6E\x7A\xCA\xFE\x70\x6F\x63\xE7\x06\xCC\x77\x26\x7B\xA0\xF3\xC0\x14\x0E\xDD\xEB\x5B\xBB\xBD\x21\x9C\x38\x35\xDD\xB3\x73\x7B\xFB\xD5\xFE\x1E\xDC\x33\xA3\x2B\x2C\x71\xAF\xE5\xAE\xFD\xE1\xDE\xCE\xB5\xED\x5B\xF0\x85\x19\xD4\x3D\x93\xF7\xCD\x20\x11\x04\x3B\x39\x5F\x53\xC3\x66\xFF\xAD\x7D\xB8\x5F\x50\xF1\xE8\x8B\x52\x1F\xA8\x61\xBF\xB3\x3B\xEC\xF6\x5F\xBB\xDD\xDB\x82\x53\x35\xF0\x64\xD8\x67\xEA\x94\xA4\xE5\xC1\x9A\xDA\x5F\xDD\xDD\xDD\xEA\xF7\x76\x60\xA1\xA6\xB2\x8D\x9D\x21\x3C\x34\x5F\x23\x23\x50\x3C\x5C\xC3\x2D\xC8\xF9\x88\x58\xD8\xCE\xED\xAD\x2D\x78\x54\x44\x2E\xA8\xD1\x9C\xEF\x60\xA0\x84\x5C\xD6\xA9\x35\x78\x7A\x79\x2B\x80\xC4\x94\x1E\x3B\x59\xBD\x74\x87\xFD\xBD\xED\x8D\x9D\xDE\xB0\xBF\x0E\x8F\x17\x83\xB6\xFA\x3B\xF0\x64\xA7\xDB\xAD\x30\xB8\xB1\xB7\xFB\x06\x3C\xD1\x2C\x1B\xE0\xB3\xF7\x54\x9D\x6F\x6C\x0C\x6F\x76\xAF\xF5\x6E\xC1\xE7\x5A\x95\xF4\x70\xBA\x53\x8D\xE8\xDD\xBA\xD5\xDF\x59\x87\x33\x35\x54\xF7\x79\xBF\x7F\xBE\x0E\x97\x58\xDF\xD9\x07\xFC\xB2\xA1\xAD\xBF\xD5\xDF\xEE\xFA\x1D\x76\xEE\x44\x7D\xEC\xCE\x7E\x7F\x6F\x08\xE7\x0B\x8E\x59\xF5\x4F\x09\x2A\x1B\xFB\xEB\x1B\x37\x36\x86\xF0\xC5\xF0\xB6\x7F\xAB\x77\xAD\x0F\x5F\x6A\xFA\xB7\x9B\xFD\x37\xE1\x69\x51\xE5\x60\x7F\x77\xA7\xBB\xD5\x7F\xB3\xBB\x7B\xFD\xFA\x7E\x7F\x08\x5F\xBE\x7F\xAC\x75\xAF\xBF\xDD\xDB\xD8\x61\xB4\x9E\xE9\x8C\x75\xF4\x77\xAF\xC3\xE2\xE4\xD8\xDE\x7A\xB7\x37\xDC\xDD\x86\x67\x4F\x4D\x77\x08\x3B\xFB\xF0\x95\xCF\x4C\x77\xDD\xDE\xD9\xB8\xB6\xBB\xDE\x87\xE7\x66\x4C\x0B\x86\xFF\xD5\x19\x5D\x41\x8D\xCF\x9F\x98\xEA\x82\xAF\x8D\xB7\x6D\xEC\x6C\x0C\xE1\xEB\xF7\x94\x6D\xB7\x7A\x7B\xFB\xDE\xE4\xBE\x71\xDF\x44\xA3\xB7\x93\x17\x4E\x4E\x34\x07\x36\x96\x26\x87\x7B\x2B\x70\x93\xC3\x83\xEE\xBF\x39\x37\xDE\x0E\x17\x5A\xFF\xBA\x7A\x41\x81\x9D\x07\x02\xD7\x79\xDD\xE1\x60\x09\x5C\x67\x68\x15\xD8\x45\x50\x4B\x6A\x89\xE0\xB4\x32\x39\xAC\xA4\xA0\xE5\x3B\x10\x76\xBE\x77\x74\x74\x04\x2B\xA9\xE2\xF7\x24\x87\x14\xAC\x75\x30\x6F\x65\xA8\xFD\x07\x13\xEB\x91\x3A\xD0\x23\x0E\x41\xEA\xD0\x8D\x08\x08\xF7\x1C\x5C\x24\x5C\xC8\x14\x13\x5C\x49\x81\x70\x25\x05\x37\xCA\x14\xC1\x79\x80\x5C\x13\xF2\xC3\xAC\xA5\xE0\x30\x53\x64\x48\xF3\x57\xE0\xF1\x93\xFF\xA4\xDD\x68\x90\x6B\x17\x5D\x4E\x81\xDF\xE6\xE2\x16\x28\x6D\x9A\x2D\xBF\x3E\xE1\x69\x65\x36\xE7\xAD\x3B\x7A\xFB\xE8\x08\x3A\x47\xCC\xAA\x30\x87\xE7\x01\xFD\x7A\xB8\x92\x6A\x02\x97\x0C\xCE\x6A\xC8\x22\x52\xEE\x97\xF8\xDA\x4A\xAA\xDC\x88\x22\x42\xDF\x9C\xC7\xEB\x14\x51\x7C\x6D\xC1\x4B\x95\x30\x55\xF9\x30\xB9\xF2\x88\x68\x52\xA4\xD7\x16\x3A\x7F\xCE\x0B\xE4\x2A\x8D\xBD\x34\xCE\x0C\x4E\x2B\xC8\x81\x30\x7C\xC3\xB5\x34\x66\x04\x2E\xCF\x5B\xB5\x24\x64\xF2\x86\xC7\x2D\xA1\x06\x25\x6B\x79\x73\x21\x57\x8C\x88\xBA\x9B\xB5\x64\xF9\xCC\x3A\x93\xE9\x65\xCC\xDA\x94\x64\x46\x2F\xD5\x61\xA0\xF6\xF2\xE8\x20\x4F\xC9\xDC\xBD\x92\x42\xB1\x1A\x69\xFE\xCC\x14\xA5\x64\x9F\x51\xF0\x72\x8A\x6D\x65\xC3\xCB\x95\x14\x2D\xA9\x82\x1B\x3F\xD2\xEB\x8F\x57\xC5\x76\xD3\x92\x72\x65\x4B\x9A\x58\x6A\x53\xEB\x4A\x8A\xA4\x99\x13\x4D\xED\x65\x3C\xC8\xDA\x82\x16\xAF\x61\xDA\xAC\xEB\x11\x35\x29\xA1\xC6\x1A\xA3\xC3\x1C\x9F\xD1\xF0\x7E\x16\xB7\xD1\xCA\xB8\x33\x1A\xB2\x76\x81\x2A\x12\x9E\xD5\x49\x16\x53\xFB\xFD\x2C\xB2\x02\x71\x05\xAD\x40\xD6\x26\x3C\xA3\x93\x3C\x7D\x29\x45\x6A\x53\xFA\xFF\x19\x71\xC2\xCE\xEF\x33\x47\xF3\x96\x94\x7D\xFA\xD3\xDB\xA1\x3D\x8D\x38\xF2\xB8\x11\x3C\xC3\x9A\xFB\x55\x86\x4B\xCC\xE3\x5D\xA7\x87\x83\xD3\xCA\x64\x8A\x89\x2F\x21\x1E\xB2\x5E\xCE\x03\x38\x7D\x91\x0D\xF3\x3C\x20\x3F\x98\x27\xB5\x0C\x2F\xB1\x66\x84\xC6\xAF\x15\x14\x54\x41\x61\xDE\x3A\xB0\xDF\x47\xC5\x99\x97\x67\x71\x45\x4C\x52\x4C\x5A\xB9\x78\x95\xE7\xBA\xE8\x22\x83\x10\xE8\xA3\xA7\x8F\x9E\xBE\x66\xFA\x48\x3A\xD0\x47\x4F\x5F\x57\xF4\x3F\xF5\xE6\x7B\x05\xD5\x48\xBE\x65\x50\x9A\x8C\x6C\x35\x36\x0F\x58\x49\xF5\x04\x9D\x4C\x0B\x99\x0C\x48\x0B\xC9\x14\xDA\xC6\x5A\x82\x14\x3D\xCD\x15\x36\x1F\x10\xEB\x62\xB4\x6E\xA2\x97\x34\x50\x2D\xC5\x52\x22\xD6\x8A\x18\x9E\x57\x55\xB2\x12\xE0\x3A\xAD\x60\xDE\x33\xC8\x22\x2F\xE3\xC1\xC7\xA0\x2A\xA3\xED\xCF\x51\xA9\x11\x1E\x16\x2A\x14\x5C\x0B\x64\xB5\x47\x56\x33\xB2\x8A\xF9\xC0\x09\x3E\xF8\x11\xD6\x32\xBC\x16\xFB\x13\xBF\x96\xDF\xBB\x64\xAA\xB5\x4A\xB0\xBC\x40\x0C\xEC\x47\x41\x84\x02\x91\xB6\x1E\x9A\x80\x4C\xDB\x63\x34\x2E\x32\x29\x7B\x1E\x71\x54\x18\x6E\x50\x82\xE8\xD1\xE1\x20\x03\xEF\x0A\x30\x05\x4B\x5E\xE4\x86\x4C\x4E\xC2\xD3\xD8\x2B\xA8\x46\x01\x6B\x11\x1B\x9D\x59\x0D\x8A\x44\x46\x00\x09\x3D\x02\x3C\x2B\x08\x52\xB7\x18\x1E\xA9\x97\x48\xF1\x6A\x75\x63\x21\x65\xE7\xC1\x7B\xE5\x75\xFE\xB8\xB6\x60\x7F\x88\x1A\x47\xEA\x40\x1D\x06\x27\x59\x73\xB6\xDE\x5D\x02\x01\xEF\x62\x5D\xEE\x6A\x37\x22\x2D\x3B\x3B\x37\xEB\xA4\xC9\x78\x47\x79\x46\x27\x35\x7F\xEA\x77\xBE\xA1\xE8\xFD\x4C\x33\x44\xAC\x93\xF7\x33\x63\x65\x6E\x31\x2D\x83\x36\xB2\x1F\x00\xF6\xBD\xBC\x27\x62\xB6\x8D\x88\x62\xF1\x03\xF6\x25\x01\xE1\x13\xBA\x60\x38\xDE\x05\x83\xB8\x60\x6F\xC4\xFF\x8E\xB1\x91\xE0\x84\x87\x6A\xE4\x77\x3F\x83\x13\x30\xF2\x44\x73\xB3\x90\x47\x2C\x77\x74\x37\x8B\xBD\x57\x6E\x38\x93\x45\xCB\x98\x25\xA4\xB3\xA6\x5A\x12\xBF\xDC\x2A\x71\x4A\xD8\x2F\x5B\x6A\xDE\x7D\x39\x05\xB2\xD4\xA8\xFC\x70\xE1\xA4\x23\x71\xA0\xAD\xD0\x29\x7E\xB9\x55\xFA\xE5\xA8\xE6\x97\x5B\xBC\x8F\xB4\xA5\x56\xE9\x97\x5B\xA9\xB2\x94\x50\x7C\x25\x55\x14\x31\x2F\x11\xB1\x75\x67\x49\xE9\x97\x9B\xEC\x97\xA9\xC5\x2E\x95\x83\x27\xA9\xB5\x05\xFB\x6F\x06\xD3\x11\x41\xE7\x0F\xC4\x8A\x55\xB1\x6F\xFD\x7B\xAE\x59\x58\x43\x8A\x1F\x11\x19\x8A\xD6\x16\xF2\x58\xE2\x90\x33\x2C\x2C\x64\x89\x5E\xA2\x06\x33\x97\x38\x3D\xCC\x9B\x03\xF1\x4D\xCE\x0C\xF2\x16\x37\xCA\x7B\xE0\xCF\xA6\xCA\x41\xD6\x76\x90\xA5\x95\xE1\x04\xF0\x2D\xD3\x6C\x94\x33\xB2\x39\xBD\x44\x96\x5F\xE7\x2A\x4F\x66\x99\x09\x9B\x82\x65\x0A\xB6\xD4\x9B\xF5\x00\x5A\x9E\x96\x56\xAD\xE2\x93\x5A\x35\x8A\xAC\x8C\x09\x9A\x58\xD2\x6C\x2B\x1B\x48\xB4\x39\x26\xB6\x2B\x96\x29\x71\x38\xC8\x13\x8A\xD7\x64\x5B\x18\x12\x4F\x5D\x8A\x6F\xD9\x93\x31\xE3\xF9\x9C\x77\xEB\xCA\x25\x83\xBC\x79\x5A\x49\xFE\x32\xC7\xFA\xFA\x2E\x93\x52\x4B\xC4\x8D\x4E\xAD\x15\xE0\x25\x2E\xC9\xE6\x1C\x66\x4D\xBD\x44\x89\xB0\x35\x28\x47\xD3\x1C\xEB\x6F\x8E\x9A\xBC\x76\x53\x68\x9E\x56\xE0\x39\xE0\xC5\xB2\x39\x4B\x73\x7E\xA8\x25\x55\xAE\xA0\xD9\xDE\x03\x1F\xFA\x13\xF0\xA1\x3F\x25\x1F\xBA\xCE\x87\xF6\x48\x8F\xB1\xA2\xC3\x34\xCB\x26\xE6\x90\xD1\xBA\xBC\x60\xFF\x3B\x42\xCB\x71\x16\x0B\x47\x2D\xDB\x72\xCA\xF9\x88\xAD\x69\x89\x54\x9D\xCE\x6F\x0A\xFE\xC6\x45\x5F\x05\x20\x43\x6A\x51\xB5\xC9\x2C\xC3\xB3\xCA\x04\xCB\x91\x6C\xC8\xB8\xF6\x80\xA7\x0F\x17\x95\xA0\x3A\xF4\xF3\x32\x54\x4B\x64\x18\x80\xC8\x03\x20\xFE\xD0\xB0\xB4\xB1\x83\x8C\xC3\x1B\x92\x1A\x50\xCC\xE2\xAA\xC1\x19\x05\xCF\xCA\xFC\x44\xD8\x60\x59\x35\x45\xA5\xA0\x3C\x2F\x57\x15\x26\x8A\x90\x97\x8B\xC4\xAB\x73\x12\x56\x5A\x71\x43\x4C\xD1\x4D\x35\x8B\x2D\x32\xAF\x59\x22\x6C\x65\xBC\x95\xF4\x20\x8B\xC3\xDE\x61\x4F\x41\x4A\xD0\xCF\x39\xA5\xA5\x84\xB9\x18\xBA\x16\xE9\x85\x5C\x79\xD9\x74\x21\x1B\xAB\xB0\x26\x56\x90\x07\x3E\xB9\x3C\x71\x25\x4A\x4C\x50\x89\x62\x29\x12\x55\x2C\x2A\x28\x15\xA0\x49\xDD\x7D\x56\x33\x3B\xFA\x79\x04\x99\x9A\xA3\x70\x33\xC8\x15\x69\x61\x8D\x9A\x8B\xCA\x04\x1A\x2C\x4E\x92\x37\x78\x1D\xD9\x0B\x62\x66\x02\x3C\xB8\x24\x6B\x39\x14\x7F\x81\xDE\x59\x28\xAF\x9F\xCC\x4A\x83\xA4\xEB\x3E\xA5\x61\x62\x26\x6F\x86\xC8\xEA\x03\x68\x1E\x91\x1A\x64\xB1\xF7\x4E\x0E\x2F\xA5\x40\xAD\x4C\x51\x92\xA1\x5E\xA2\x98\x59\x1F\xB8\x37\x0B\xD1\xC1\x03\xE1\x24\x7C\x53\x41\xA8\x51\x10\x92\x75\xB2\x98\x94\x7B\x73\x20\x81\x72\x34\xC8\x50\x4E\x0C\x99\xA6\x86\x83\x6F\x71\xBC\xF5\xEB\x71\x48\x4A\x32\xB4\x14\x93\x15\xA8\x08\x79\xB2\x97\x39\xB8\x06\xE3\xB7\xC5\x77\x52\xA4\x16\x63\xDE\xF2\x11\x1F\xC5\xDB\x1A\xFB\x7D\x44\x33\x16\xE1\x6B\xB9\x0F\xD6\x37\x26\xBB\x69\x31\x34\x0F\x1B\x9B\x4F\xA6\x5D\x92\xA1\x43\xCE\xF0\x79\x83\x00\x61\x6D\x77\x4A\xC4\x41\x32\xCC\x86\x38\xE9\xA0\xE1\x60\x78\xCC\x36\x16\x5B\x33\xCC\x91\x23\x5C\x91\xB4\x38\xB8\x68\xFF\xA7\xE0\x8E\x93\xA3\xD1\x20\xC7\x70\x64\xF2\x40\xCF\xC5\x00\x00\xA8\x2A\x02\xF3\xF6\xFF\x8A\x28\xF5\x34\xA6\xC8\xFD\x93\xDC\x84\x34\x38\xD0\x35\x39\x16\x59\x94\x09\x6E\x7E\x8C\x98\x5D\x2B\x73\x87\x49\x4A\x38\x4E\x09\x73\x95\x81\x8F\x84\x58\x52\x52\xA5\xFB\xB5\x65\x4A\xC6\xCF\x33\xDA\xA7\x68\x67\x75\x52\xA6\x6C\x27\xF9\xB4\x81\x61\x63\x81\x43\xF6\x6C\x60\x1F\xE2\xD6\x24\xB4\x22\xEF\x47\x43\xE8\x14\x77\xA2\xEF\xEC\x54\x9D\xBC\x15\xD1\x6F\x45\xB4\x0F\x4F\x74\x3E\x27\x9D\xDF\x55\xBE\xF7\x31\xEE\x6D\x87\x5E\x71\x59\x86\x3D\x24\xD3\x55\xBC\x4A\x22\xA9\xA7\xAA\xC6\xA0\xEF\x84\xCE\x9F\xF9\x4C\x80\xD0\x0F\x43\x76\xBE\x86\xD0\xFE\x16\x5B\x0A\x89\x30\xED\x1C\xBD\x7F\xC2\x85\x1C\xD9\x35\x63\xE5\x9F\x54\x65\x13\x4E\x36\xAF\xD7\x3D\x88\xDE\x15\xE1\xA0\x50\x7E\xD8\xAB\xE3\x9A\x87\x7A\x88\xE1\xA9\x39\xD6\x8C\x84\x59\x01\x2F\x5A\xA7\x04\x52\x42\x04\x38\x60\x3E\x40\x42\x04\x81\x7D\x17\x31\xAA\xA1\xE3\x07\x89\x44\x6D\xC2\x10\x47\xC2\x1D\x03\xC7\x10\x08\x31\x04\xAA\x18\xA2\xC4\xD1\x24\x03\xCE\x81\x82\xB3\xF5\xC9\x62\x66\x1C\x64\x91\x08\x03\x03\xE2\x3C\x9E\xA0\x74\x3C\x2C\x0C\x50\xC4\xC2\x44\x22\xA8\x97\x84\xE7\xE5\x50\x49\x02\x5E\x12\x42\x6F\x15\x49\x29\x4C\xEC\xAD\xE2\x17\xCC\x7E\x61\x88\xCA\x43\xAE\xFD\x4E\x6A\x0F\x3E\x22\x38\x98\x62\xEF\xD5\x03\x1F\xD4\x03\x9F\xF9\xE4\x81\x02\xA6\x02\x9F\xA9\x47\x0B\x5E\xE7\xE3\x02\x46\x91\xD6\x16\x11\xC3\x9B\xA1\x04\x0E\xFB\x7B\x0A\x6D\x21\x63\xCD\x4D\x48\x44\x0B\x6E\x42\x79\x7F\x6D\x38\x5A\xC4\x83\x5C\x62\x65\x42\xDC\x30\x1A\xE4\xCD\x22\x5A\x34\x78\xEF\xB6\x08\x4F\xC8\x35\x8E\x5C\xD6\x48\xD8\xA5\xB8\x08\x18\x91\x0F\x18\x9A\x03\x6F\x66\x25\x60\x34\x28\x2A\xE5\xF7\xB9\xB5\x3B\x10\x56\x6B\x84\xDA\x05\xA1\x66\x08\x18\x11\x07\x8C\x88\x2C\x47\x0A\x4B\x4D\x7E\x34\xA9\x1D\x02\x06\x53\x64\x24\x25\x66\x98\x8C\x23\x2D\x25\x0C\x58\x83\xAC\xC4\x8C\xD6\xA2\xE4\xC6\x72\x63\x32\xC3\xDC\xED\x3D\x7E\x33\x08\x0C\x84\x0B\xA7\x0B\x53\xAF\x76\x31\x78\x75\xFB\xED\x1B\xC0\xB4\x3F\x42\xD4\x9C\x5E\xE9\xEA\x02\xC4\x78\xD7\x6C\x82\x6B\x2E\x72\x0E\x31\x00\x3D\x76\x41\xE0\xF5\x9D\x6B\x3F\x82\x53\xB0\xE0\xDB\x4C\xA6\x6D\xB9\x72\x14\x52\x1E\xBF\x72\x24\x1E\x81\x73\x4C\x26\xAE\x29\x5A\xF4\x47\x4B\x66\x3B\x5A\x94\x63\xAD\x96\x21\xF6\x51\xC4\x11\x81\x84\x57\x2D\x61\x7B\x90\xF3\x21\x9C\xD1\x42\x82\x4D\xFB\xED\xEA\xF4\xAA\x82\xB7\x1D\xBF\xE8\x53\x7C\xFE\xBB\x9C\x6A\x99\x2B\xCE\x44\x4E\xC5\x8A\xF5\xAF\x8A\x4B\x0D\x37\x9A\xB7\x0E\xED\x4F\x10\x1B\x61\xB5\x22\xAB\xCC\x55\xB1\x9A\xC4\x76\x96\x5E\x8C\xDC\xB7\x16\x37\x85\x84\x9B\x1C\x73\x08\x36\xC3\x19\x27\x8F\x17\x38\x69\x2B\xF2\x53\xDE\x2A\x81\xAD\xF0\x2D\x11\xCE\xDC\xA9\xA1\x3B\x75\x9B\x12\xFF\xFC\x56\xAA\xC3\x9A\xD4\x10\x7D\xBA\x91\xE4\x25\x66\x6D\xA1\x64\x71\x8E\x6D\xFC\xC5\x01\x1F\x7D\x5D\x6B\xCD\x9E\x09\x47\x7F\xF7\x3A\xC7\xDD\xFB\x2F\xA7\xE0\x90\x70\xE8\x7E\xFB\xE8\x48\xBD\xC6\xEB\xA3\xC0\xDA\x5C\xB5\xEC\x76\xDD\xBF\xE0\x6B\xEE\x83\x91\x9F\x1D\xAF\xD5\x29\xED\x59\x7F\xA9\xD0\x91\xCC\x63\xD3\xDE\x1B\xAE\x4B\xB8\x69\x53\xDA\x06\xA1\x2D\x8C\x90\xEE\xEF\xD8\x4B\xC2\x40\xB0\x1A\xE9\xCE\x4D\x18\x4F\x8A\x77\x0C\xA7\xF0\xCA\x1F\x9D\x39\x89\xF5\xD7\x3D\xAE\x33\xC8\xCB\x1C\x8B\xED\x42\xDB\x2B\xA8\xC3\x56\xEE\xE4\x58\x5F\x42\x76\x75\x67\x50\x78\x21\x49\xBD\xCF\x01\x74\xFE\xA2\x38\x81\x29\xD9\x63\x62\x19\x4C\xAA\x98\x5C\xBA\x4E\xFB\x3D\xE4\x28\x85\x92\xD3\x57\x6C\xAA\xC0\xA6\x33\x97\xE4\x44\x8F\xA4\x98\xEC\xCF\x3C\x59\xBF\xA8\xF8\xAE\x73\x80\x55\xAB\x6F\x50\x13\xC3\x40\x5A\x75\xAD\x55\x68\x3B\x23\x9E\xCD\xA7\x0B\xF6\xBF\x8C\x58\x57\x10\x71\x51\x25\x3E\x41\xF2\x97\x78\xE7\x2F\x06\x5A\xC1\xC6\xFD\x3C\x45\xE3\x50\xF8\x1B\xA8\x4C\x59\x6F\xF0\xA2\xBB\xA7\xA6\x67\x06\xA4\xDA\x58\x8C\x13\xF3\x2A\x10\x2B\xBA\x2B\x09\x27\x00\x0F\x48\x9B\x09\xA4\xD5\x71\x48\x1B\x8F\x74\x5B\xDB\x4A\xBD\x3E\xE3\x62\xF9\x42\xCA\xEC\x15\x92\xB3\x3E\x2B\xA9\x9F\xBC\x58\x02\x5D\x71\x5F\x64\x8E\x14\x6D\xE6\x9A\x82\x01\x18\x7F\xD6\xF9\x04\xFC\xE4\x32\x4D\xCE\x19\x26\x9C\x33\xC9\xAC\xF9\xAB\x8E\xC0\x59\x5E\x31\x55\x9C\xB0\x58\x6C\x5B\xA0\x4B\xCA\xD1\x9E\x80\xFB\x21\x8C\xA3\xAB\x0B\xBD\x68\xE6\x2E\x66\xEE\xD0\xBB\x00\x16\xA8\xE1\xCE\xAF\xA6\x40\x0D\x77\xF6\xA2\xDC\x67\xFA\x69\xCA\x4F\xAB\x4D\x09\xFA\x8B\xA9\xB2\xEE\x90\xE5\xB0\xB3\xD0\x63\x32\xA2\xA4\xC5\xC5\xDA\xA4\x03\xE6\x55\x7E\xAB\x33\x6C\x63\x1D\x74\xCE\xCE\x17\x55\xDB\x45\xFE\x1A\xEA\x67\x1A\x63\x39\x3B\x17\xBB\x8B\x3D\x9A\xCB\x45\xAE\xE0\x6E\xD8\x25\x8B\x16\xC4\xE9\x4A\x7A\x27\x98\x78\x83\xC8\x23\xD1\x19\x3B\xC4\x73\x00\x79\xEC\xF2\x55\x16\x9A\x62\xF7\xCF\xC0\xE2\xC6\x8E\x2E\xA5\x9A\x62\xF7\x4B\x58\x4D\x35\xE9\x92\xA8\xC4\xFB\x4D\x8A\x3C\xF8\x01\x0F\xE9\x8C\xB9\x33\x96\x4E\x36\xEA\xF8\x1C\x80\x7B\x65\x90\xC7\xEE\x1F\xE1\xB2\x07\x94\xE9\xCF\xBD\x0C\xA6\xF6\x37\xF6\xF2\x71\x7F\x50\x7E\x42\xAD\x05\x0C\x20\xB0\x7A\xD5\x20\x5C\xA7\x7A\x96\x54\xE0\x37\x22\x33\x20\xB5\xC9\x2E\x41\x8D\xB9\x03\x35\xD3\x1D\xA8\x49\x77\x30\x36\x4C\x8F\x0F\x13\xF5\x16\x2B\x4A\xEA\x06\xFE\x52\xA2\x84\x85\x6D\x2B\xF6\xBE\xE2\x3D\xF6\x15\x95\xCB\xEA\xE4\x41\x25\x3A\xA8\xA4\x32\x9C\x28\x6C\x8E\xE9\x1F\xB0\xF8\x5F\xCB\xFE\x3F\x27\xD7\xBA\xAF\x0C\xF2\x86\xFB\x27\xB8\xCC\x16\x3A\xF7\xEB\x1A\x8A\x3F\x03\xF5\xBF\x08\x3E\xE6\x2F\x86\x46\xF1\x35\x09\xCF\xA6\x7C\xB6\xFC\x8B\x65\x47\x40\xE8\x2D\x38\xE4\xC0\xB2\x2D\x14\x47\x83\xCE\x5F\x06\x4C\x58\x38\x8A\x24\x77\xA8\x99\x4B\x21\x64\xB1\x7D\xB1\x9D\xB2\xE1\x74\xFE\xDA\x1F\xD7\xBC\x7B\xE9\xFC\x4D\x71\x78\xAB\x23\x67\x27\x1B\xDA\x93\x0D\xCD\xC9\x86\x16\x53\xE4\x73\x3B\xA9\x4D\x32\x03\x17\x5D\x4A\x8D\xFB\xE1\xDB\x92\xA9\xBA\xC8\x07\xAD\xD4\x78\x9D\x86\x7D\x18\xC9\x44\x33\x31\xD1\x5C\x4A\xB5\xFB\xDD\x30\xD1\x84\x89\x7A\x6C\xA2\x0F\x05\x6A\x7A\xA2\x72\x3F\x98\x98\xA8\x66\x4C\xD4\x93\xBC\x37\x26\x1B\x38\x81\x42\xFB\x08\x94\x69\x5D\xC8\xF5\xF8\xD1\x21\xC5\x27\x96\xC4\x76\x15\x8E\xD4\xE1\xF2\x68\x19\xFD\x71\xFD\xFC\x6A\xAE\x16\x32\xBD\xEC\xF3\x7E\x45\xF8\xFF\xBC\x5B\x90\x1F\x0E\x48\x6D\x66\x6A\xD9\x1F\xBB\xCC\x72\xEB\x90\xE0\x29\x80\x83\xE5\x17\x0F\x32\x33\x9D\x46\x91\x21\x7D\x68\x7F\xA1\x23\x1C\xA9\x03\xB9\xE5\x1F\x5D\xF0\x06\xF1\x9F\x1F\x14\x5F\x5E\x18\x5B\xF4\x42\x61\x46\xB5\xB5\x6B\x6D\x21\x6D\xCA\xA3\xB1\x60\x6F\xC2\x88\xC7\x97\xEE\x10\xF7\xB8\x17\x07\xEF\xBD\x93\x99\x22\x6F\xE2\x24\xDD\x7B\xFC\x3B\xBC\x25\x98\xEA\xAA\xBF\x63\xF0\x79\x97\x44\x9C\x6A\x91\x86\x5A\x2A\x67\xF2\xEA\x7F\xF8\x23\xFE\x7B\xFF\x85\xCC\xD4\xC6\xCC\x66\xA4\xC1\x79\x5E\x60\xE0\xCE\x3B\x59\xA3\xC6\xD9\x4F\xA7\xF8\x89\x49\x53\xE3\xCE\x3B\x59\xEC\x19\x42\xEB\x30\x4B\x8A\x0B\x6C\x3F\xB6\x29\x69\x61\xE4\xCE\x7A\x7E\x23\x9F\x11\x88\xA7\x6A\xBA\x51\x96\xF0\x94\x88\x9A\x4C\x51\xAE\x08\x51\x7E\x01\x51\x83\xBC\xC9\xAC\x8B\xD7\x6F\xB2\x37\x40\x6A\x6E\xFA\x7B\x52\xC9\x60\x5B\x5B\xC2\xE5\xC0\xBD\xC8\x19\x14\x9F\x2A\x79\x8C\x5C\xD3\xA4\x50\x50\x61\x2E\xF9\xAB\xE6\x75\x5B\x5B\xD2\xC6\x7A\xF5\xC7\x12\x2B\x49\x68\xB2\xF5\xDE\x9D\x2C\xB6\x14\xDB\xBF\x42\xA5\x46\xEA\xB0\x76\x8F\x36\x99\xE5\xBA\x27\x57\x25\xBB\xFD\x7B\xF0\xCF\x0F\xE5\x39\x06\xC9\x98\x09\xEA\x85\xCC\x2C\x43\x16\xF9\x54\xDA\x9B\xA0\x96\x7B\x35\xD2\x9B\x62\x9D\xC2\x1A\x9B\xA0\x2A\x4C\x30\x2A\x2F\x7D\xE5\xDE\x4F\xF8\x8C\xC8\x1C\x76\x7E\x52\x38\x07\xC2\xCE\x07\xFC\xBD\x73\x47\x6E\x57\x6E\xA2\x91\x30\xD8\xF9\xE3\xC2\x31\x71\x3E\x51\x9C\x79\x3A\x7F\x1B\xAE\x8B\xED\xAA\x1C\x77\x5A\x17\x25\x68\xF9\xCD\x9A\x4A\x26\x03\x64\x3A\x7F\xE7\x2F\x03\x24\x13\x24\xEC\xFC\xA9\xDC\x81\xA1\x44\x60\xB9\xB5\xE5\x80\xFB\x03\xAD\xF4\x48\x1D\x16\x3F\xA7\x13\xBA\x43\x4E\xCE\x1B\xE5\xA5\x58\xF5\x8B\xE9\x5C\x02\xA8\xB4\x89\x1A\x31\xD8\xCE\x8F\x83\x8F\xC3\xCE\x3B\x85\xB7\x2B\xBE\xF9\x28\xD0\x0E\xBF\x1C\x6D\xE6\xA6\x80\x9D\x0F\x3C\x9A\x60\xE0\x7F\xB8\x7B\x72\x35\x8D\x09\x05\x76\x7E\x7E\x28\x4F\x71\xAF\x40\xA6\x82\x5D\x07\xD8\x71\x21\x8B\x96\x41\xF6\x89\x21\xFC\xB6\x9C\xC4\x64\x57\x1B\xC2\xCD\x0C\xB9\x8B\x0F\xBA\x63\x3B\x3F\x2E\x7F\x56\x2C\x6C\x48\xCC\xA3\x06\xBB\x5C\x6F\x79\x76\x71\xB3\xF3\xD3\xA2\xB9\xF3\x73\x0F\x7B\x1B\x6D\x5D\x0B\xA2\x03\x33\xA6\x03\xED\x75\xA0\xC7\x74\x60\x0A\x1D\xE8\x8F\xD1\xC1\xBC\x25\x25\xC7\x47\x33\xA6\xFD\xDF\xC1\x42\xFD\x7F\x32\xA6\xFE\x63\x96\x4E\x3E\x6A\x69\x39\xB4\x86\x29\xAE\xED\x13\xA9\xF0\x3A\xCE\x9C\x26\xEC\xDC\x9D\x66\xCF\xFE\x0A\xE2\xE8\x31\x5F\x31\x42\x9B\xB9\x7A\xFC\xA8\xFA\x55\xA0\x5D\xDE\xAD\xF1\xE3\x94\xF8\x6D\xDD\x91\x0B\x16\xF9\xA8\x2F\x23\xFE\x97\x06\x61\x3A\x58\xFB\x04\x4A\x79\xE3\x23\x1B\x5B\x5B\xFD\x1B\xBD\x2D\x92\xF2\x31\x90\x8A\xB1\xE1\xDE\xED\x3E\x5C\xEF\x6D\xED\xF7\x01\xDE\x6D\x99\x9D\xDE\x76\x1F\x7F\xDC\x5A\x81\xAA\xF6\x12\xA7\x6B\x21\x55\xAD\xC0\x51\x97\xF5\x8D\x66\x66\x79\x63\x34\x5D\xDD\x18\x4F\x14\x37\x36\x3E\xA2\xB6\x31\x99\x55\xDA\xD8\x1C\xAF\x6C\x6C\xCD\x2E\x6C\xB4\x33\xEB\x1A\xDB\x13\x65\x8D\xE9\x71\x55\x8D\x73\xC7\x14\x35\xCE\x1F\x57\xD3\xD8\x99\x2A\x69\x3C\x71\x5C\x45\xE3\xAC\xAA\x45\x5F\x50\x75\xEF\xB1\xF5\x8C\xF7\x1D\x57\xC0\x78\xF2\xD8\xFA\xC5\xFB\x27\xCB\x17\x1F\x98\xA8\x5E\x3C\x35\xAB\x78\xF1\x33\x13\xB5\x8B\x0F\x4E\x96\x2E\x2E\x4C\x57\x2E\x3E\x34\x5E\xB8\xF8\xF0\x64\xDD\xE2\x23\x53\x65\x8B\x8F\x96\x55\x8B\x54\x2F\x5A\xCC\x26\x6A\x16\xF3\xA9\x92\xC5\xC7\x6A\x15\x8B\x8F\xCF\x2E\x58\x7C\x62\xAA\x44\xF1\xB3\x55\x85\xE2\xE7\x66\x14\x28\x9E\xAE\xD5\x27\x9E\x99\x2A\x4F\xFC\xFC\x44\x75\xE2\xD9\xC9\xE2\xC4\x73\xC7\xD5\x26\x9E\x9F\x2E\x4D\x7C\xB2\x56\x4B\xF9\x85\x7A\x85\xEA\x53\xB5\x92\xC5\x2F\xD6\x2B\x16\xBF\x54\x2F\x58\x7C\xBA\xAA\x57\xFC\xF2\xAC\x72\xC5\x67\x8E\xA9\x56\x5C\x9C\x2A\x56\x7C\xF6\x98\x5A\xC5\xAF\x1C\x5B\xAA\xF8\xDC\xF1\x95\x8A\xB3\xAA\x11\xBD\xD2\x9E\x3F\xB6\x4E\xF1\x6B\xD3\x65\x8A\x5F\x9F\xAE\x52\xFC\xC6\x8C\x22\xC5\x17\x66\xD6\x28\x2E\xCD\x2E\x51\x74\x33\x2B\x14\x97\x27\x5B\x87\xBB\x9B\xFD\x9D\x6F\xCE\x2E\x5B\xBC\x30\x51\xB5\xF8\xBF\x01\x00\x00\xFF\xFF\xC6\x17\x79\xC4\x15\x2F\x00\x00") diff --git a/internal/compiler/wasm/opa/opa.wasm b/internal/compiler/wasm/opa/opa.wasm new file mode 100755 index 0000000000000000000000000000000000000000..06a656d2ee497283b74f892622e1f481a73c43e4 GIT binary patch literal 12053 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 /etc/apt/sources.list.d/llvm.list' + +RUN curl -L https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - + +RUN apt-get update && \ + apt-get install -y \ + cmake \ + clang-8 \ + lld-8 + +ENV CC=clang-8 +ENV CXX=clang-8 +ENV LLD=wasm-ld-8 +ENV AR=llvm-ar-8 +ENV RANLIB=llvm-ranlib-8 + +RUN ln -s /usr/bin/clang-8 /usr/bin/clang && \ + ln -s /usr/bin/clang++-8 /usr/bin/clang++ && \ + ln -s /usr/bin/clang-cpp-8 /usr/bin/clang-cpp + +RUN git clone https://github.com/WebAssembly/wabt && \ + cd wabt && \ + git checkout 1.0.5 && \ + git submodule update --init && \ + make + +ENV PATH="/wabt/out/clang/Debug:${PATH}" + +WORKDIR /src \ No newline at end of file diff --git a/wasm/Makefile b/wasm/Makefile new file mode 100644 index 0000000000..6b5f6f9e73 --- /dev/null +++ b/wasm/Makefile @@ -0,0 +1,79 @@ +DOCKER := docker + +WASM_BUILDER_REPOSITORY := openpolicyagent/opa-wasm-builder +WASM_BUILDER_VERSION := 1.0 +WASM_BUILDER_IMAGE := $(WASM_BUILDER_REPOSITORY):$(WASM_BUILDER_VERSION) +WASM_OBJ_DIR := _obj + +$(shell mkdir -p $(WASM_OBJ_DIR)) + +CFLAGS := \ + -O3 \ + -nostdinc \ + -nodefaultlibs \ + --target=wasm32-unknown-unknown-wasm + +.PHONY: all +all: build test + +.PHONY: clean +clean: + rm -fr $(WASM_OBJ_DIR) + +.PHONY: builder +builder: Dockerfile + @$(DOCKER) build -t $(WASM_BUILDER_IMAGE) -f Dockerfile . + +.PHONY: build +build: + @$(DOCKER) run -it --rm -v $(CURDIR):/src $(WASM_BUILDER_IMAGE) make $(WASM_OBJ_DIR)/opa.wasm + +.PHONY: test +test: + @$(DOCKER) run -it --rm -v $(CURDIR):/src $(WASM_BUILDER_IMAGE) make $(WASM_OBJ_DIR)/opa-test.wasm + @$(DOCKER) run -it --rm -e VERBOSE=$(VERBOSE) -v $(CURDIR):/src -w /src node:8 node test.js $(WASM_OBJ_DIR)/opa-test.wasm + +.PHONY: hack +hack: + @$(DOCKER) run -it --rm -v $(CURDIR):/src $(WASM_BUILDER_IMAGE) + +$(WASM_OBJ_DIR)/malloc.wasm: src/malloc.c + @$(CC) $(CFLAGS) -c $^ -o $@ + +$(WASM_OBJ_DIR)/string.wasm: src/string.c + @$(CC) $(CFLAGS) -c $^ -o $@ + +$(WASM_OBJ_DIR)/json.wasm: src/json.c + @$(CC) $(CFLAGS) -c $^ -o $@ + +$(WASM_OBJ_DIR)/value.wasm: src/value.c + @$(CC) $(CFLAGS) -c $^ -o $@ + +$(WASM_OBJ_DIR)/opa.wasm: $(WASM_OBJ_DIR)/malloc.wasm \ + $(WASM_OBJ_DIR)/value.wasm \ + $(WASM_OBJ_DIR)/string.wasm \ + $(WASM_OBJ_DIR)/json.wasm + @wasm-ld-8 \ + --allow-undefined-file=src/undefined.symbols \ + --import-memory \ + --no-entry \ + --export-all \ + $^ \ + -o $@ + @wasm2wat $(WASM_OBJ_DIR)/opa.wasm > $(WASM_OBJ_DIR)/opa.wast + +$(WASM_OBJ_DIR)/test.wasm: tests/test.c + @$(CC) $(CFLAGS) -I src -c $^ -o $@ + +$(WASM_OBJ_DIR)/opa-test.wasm: $(WASM_OBJ_DIR)/test.wasm \ + $(WASM_OBJ_DIR)/malloc.wasm \ + $(WASM_OBJ_DIR)/value.wasm \ + $(WASM_OBJ_DIR)/string.wasm \ + $(WASM_OBJ_DIR)/json.wasm + @wasm-ld-8 \ + --allow-undefined-file=tests/undefined.symbols \ + --import-memory \ + --no-entry \ + --export-all \ + $^ \ + -o $@ diff --git a/wasm/README.md b/wasm/README.md new file mode 100644 index 0000000000..a490b3156d --- /dev/null +++ b/wasm/README.md @@ -0,0 +1,42 @@ +# OPA-WASM + +This directory contains a library that implements various low-level +operations for policies compiled into WebAssembly (WASM). Specifically, the +library implements: + +* JSON parsing +* JSON AST (e.g., comparison, iteration, lookup, etc.) +* String operations +* Memory allocation + +This library does not make any backwards compatibility guarantees. + +## Development + +You should have Docker installed to build and test changes to the library. We +commit the output of the build (`opa.wasm`) into the repository so it's +important for the build output to be reproducible. + +You can build the library by running `make build`. This will produce WASM +executables under the `_obj` directory. + +You can test the library by running `make test`. By default the test runner +does not print messages when tests pass. If you run `make test VERBOSE=1` it +will log all of the tests that were run. + +You can run `make hack` to start a shell inside the builder image. This is +useful if you need to interact with low-level WASM tooling like +`wasm-objdump`, `wasm2wat`, etc. or LLVM itself. + +You must manually push the builder image if you make changes to it (run `make +builder` to produce a new Docker image). + +## Vendoring + +If you make changes to the library, run the `make generate` in the parent +directory and commit the results back to the repository. The `generate` +target will: + +1. Build the OPA-WASM library +2. Copy the library into the [internal/compiler/wasm/opa](../internal/compiler/wasm/opa) directory. +3. Run the tool to generate the [internal/compiler/wasm/opa/opa.go](../internal/compiler/wasm/opa/opa.go) file. \ No newline at end of file diff --git a/wasm/src/json.c b/wasm/src/json.c new file mode 100644 index 0000000000..947a17678f --- /dev/null +++ b/wasm/src/json.c @@ -0,0 +1,497 @@ +#include "malloc.h" +#include "string.h" +#include "value.h" +#include "json.h" + +static opa_value *opa_json_parse_token(opa_json_lex *ctx, int token); + +int opa_json_lex_offset(opa_json_lex *ctx) +{ + return ctx->curr - ctx->input; +} + +int opa_json_lex_remaining(opa_json_lex *ctx) +{ + return ctx->len - opa_json_lex_offset(ctx); +} + +int opa_json_lex_eof(opa_json_lex *ctx) +{ + return (ctx->curr - ctx->input) >= ctx->len; +} + +int opa_json_lex_read_atom(opa_json_lex *ctx, const char *str, int n, int token) +{ + if (opa_json_lex_remaining(ctx) >= n) + { + if (opa_strncmp(str, ctx->curr, n) == 0) + { + ctx->curr += n; + return token; + } + } + return OPA_JSON_TOKEN_ERROR; +} + +void opa_json_lex_read_digits(opa_json_lex *ctx) +{ + while (!opa_json_lex_eof(ctx) && opa_isdigit(*ctx->curr)) + { + ctx->curr++; + } +} + +int opa_json_lex_read_unicode(opa_json_lex *ctx) +{ + if (opa_json_lex_remaining(ctx) >= 4) + { + for (int i = 0; i < 4; i++) + { + if (!opa_ishex(ctx->curr[i])) + { + return -1; + } + } + ctx->curr += 4; + return 0; + } + + return 1; +} + +int opa_json_lex_read_number(opa_json_lex *ctx) +{ + ctx->buf = ctx->curr; + + // Handle sign component. + if (*ctx->curr == '-') + { + ctx->curr++; + + if (opa_json_lex_eof(ctx)) + { + goto err; + } + } + + // Handle integer component. + if (*ctx->curr == '0') + { + ctx->curr++; + } + else if (opa_isdigit(*ctx->curr)) + { + opa_json_lex_read_digits(ctx); + } + else + { + goto err; + } + + if (opa_json_lex_eof(ctx)) + { + goto out; + } + + // Handle fraction component. + if (*ctx->curr == '.') + { + ctx->curr++; + opa_json_lex_read_digits(ctx); + + if (opa_json_lex_eof(ctx)) + { + goto out; + } + } + + // Handle exponent component. + if (*ctx->curr == 'e' || *ctx->curr == 'E') + { + ctx->curr++; + + if (opa_json_lex_eof(ctx)) + { + goto err; + } + + if (*ctx->curr == '+' || *ctx->curr == '-') + { + ctx->curr++; + + if (opa_json_lex_eof(ctx)) + { + goto err; + } + } + + opa_json_lex_read_digits(ctx); + } + +out: + ctx->buf_end = ctx->curr; + return OPA_JSON_TOKEN_NUMBER; + +err: + return OPA_JSON_TOKEN_ERROR; +} + +int opa_json_lex_read_string(opa_json_lex *ctx) +{ + if (*ctx->curr != '"') + { + goto err; + } + + ctx->buf = ++ctx->curr; + + while (1) + { + if (opa_json_lex_eof(ctx)) + { + goto err; + } + + char b = *ctx->curr; + + switch (b) + { + case '\\': + ctx->curr++; + + if (opa_json_lex_eof(ctx)) + { + goto err; + } + + b = *ctx->curr; + + switch (b) + { + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + ctx->curr++; + break; + case 'u': + ctx->curr++; + if (opa_json_lex_read_unicode(ctx) != 0) + { + goto err; + } + break; + default: + goto err; + } + + case '"': + goto out; + + default: + if (b < ' ' || b > '~') + { + goto err; + } + ctx->curr++; + break; + } + } +out: + ctx->buf_end = ctx->curr++; + return OPA_JSON_TOKEN_STRING; + +err: + return OPA_JSON_TOKEN_ERROR; +} + +int opa_json_lex_read(opa_json_lex *ctx) +{ + while (!opa_json_lex_eof(ctx)) + { + char b = *ctx->curr; + switch (b) + { + case 'n': + return opa_json_lex_read_atom(ctx, "null", 4, OPA_JSON_TOKEN_NULL); + case 't': + return opa_json_lex_read_atom(ctx, "true", 4, OPA_JSON_TOKEN_TRUE); + case 'f': + return opa_json_lex_read_atom(ctx, "false", 5, OPA_JSON_TOKEN_FALSE); + case '"': + return opa_json_lex_read_string(ctx); + case '{': + ctx->curr++; + return OPA_JSON_TOKEN_OBJECT_START; + case '}': + ctx->curr++; + return OPA_JSON_TOKEN_OBJECT_END; + case '[': + ctx->curr++; + return OPA_JSON_TOKEN_ARRAY_START; + case ']': + ctx->curr++; + return OPA_JSON_TOKEN_ARRAY_END; + case ',': + ctx->curr++; + return OPA_JSON_TOKEN_COMMA; + case ':': + ctx->curr++; + return OPA_JSON_TOKEN_COLON; + default: + if (opa_isdigit(b) || b == '-') + { + return opa_json_lex_read_number(ctx); + } + else if (opa_isspace(b)) + { + ctx->curr++; + continue; + } + return OPA_JSON_TOKEN_ERROR; + } + } + + return OPA_JSON_TOKEN_EOF; +} + +void opa_json_lex_init(const char *input, size_t len, opa_json_lex *ctx) +{ + ctx->input = input; + ctx->len = len; + ctx->curr = input; + ctx->buf = NULL; + ctx->buf_end = NULL; +} + +long long opa_json_parse_int(const char *buf, int len) +{ + int i = 0; + int sign = 1; + + if (buf[i] == '-') + { + sign = -1; + i++; + } + + long long n = 0; + + for (; i < len; i++) + { + n = (n * 10) + (long long)(buf[i] - '0'); + } + + return n * sign; +} + +double opa_json_parse_float(const char *buf, int len) +{ + double sign = 1.0; + int i = 0; + + if (buf[i] == '-') + { + sign = -1.0; + i++; + } + + // Handle integer component. + double d = 0.0; + + for (; i < len && opa_isdigit(buf[i]); i++) + { + d = (10.0 * d) + (double)(buf[i] - '0'); + } + + d *= sign; + + if (i == len) + { + return d; + } + + // Handle fraction component. + if (buf[i] == '.') + { + i++; + + double b = 0.1; + double frac = 0; + + for (; i < len && opa_isdigit(buf[i]); i++) + { + frac += b * (buf[i] - '0'); + b /= 10.0; + } + + d += (frac * sign); + + if (i == len) + { + return d; + } + } + + // Handle exponent component. + i++; + int exp_sign = 1; + + if (buf[i] == '-') + { + exp_sign = -1; + i++; + } + else if (buf[i] == '+') + { + i++; + } + + int e = 0; + + for (; i < len; i++) + { + e = 10 * e + (int)(buf[i] - '0'); + } + + // Calculate pow(10, e). + int x = 1; + + for (; e > 0; e--) + { + x *= 10; + } + + return d * (double)(exp_sign * x); +} + +opa_value *opa_json_parse_number(const char *buf, int len) +{ + for (int i = 0; i < len; i++) + { + if (buf[i] == '.' || buf[i] == 'e' || buf[i] == 'E') + { + return opa_number_float(opa_json_parse_float(buf, len)); + } + } + + return opa_number_int(opa_json_parse_int(buf, len)); +} + +opa_value *opa_json_parse_array(opa_json_lex *ctx) +{ + opa_value *ret = opa_array(); + opa_array_t *arr = opa_cast_array(ret); + int sep = 0; + + while (1) + { + int token = opa_json_lex_read(ctx); + + switch (token) + { + case OPA_JSON_TOKEN_ARRAY_END: + return ret; + case OPA_JSON_TOKEN_COMMA: + if (sep) + { + sep = 0; + continue; + } + } + + opa_value *elem = opa_json_parse_token(ctx, token); + + if (elem == NULL) + { + return NULL; + } + + opa_array_append(arr, elem); + sep = 1; + } +} + +opa_value *opa_json_parse_object(opa_json_lex *ctx) +{ + opa_value *ret = opa_object(); + opa_object_t *obj = opa_cast_object(ret); + int sep = 0; + + while (1) + { + int token = opa_json_lex_read(ctx); + + switch (token) + { + case OPA_JSON_TOKEN_OBJECT_END: + return ret; + case OPA_JSON_TOKEN_COMMA: + if (sep) + { + sep = 0; + continue; + } + } + + opa_value *key = opa_json_parse_token(ctx, token); + + if (key == NULL) + { + return NULL; + } + + token = opa_json_lex_read(ctx); + + if (token != OPA_JSON_TOKEN_COLON) + { + return NULL; + } + + token = opa_json_lex_read(ctx); + opa_value *value = opa_json_parse_token(ctx, token); + + if (value == NULL) + { + return NULL; + } + + opa_object_insert(obj, key, value); + sep = 1; + } +} + +opa_value *opa_json_parse_token(opa_json_lex *ctx, int token) +{ + switch (token) + { + case OPA_JSON_TOKEN_NULL: + return opa_null(); + case OPA_JSON_TOKEN_TRUE: + return opa_boolean(TRUE); + case OPA_JSON_TOKEN_FALSE: + return opa_boolean(FALSE); + case OPA_JSON_TOKEN_NUMBER: + return opa_json_parse_number(ctx->buf, ctx->buf_end - ctx->buf); + case OPA_JSON_TOKEN_STRING: + return opa_string(ctx->buf, ctx->buf_end - ctx->buf); + case OPA_JSON_TOKEN_ARRAY_START: + return opa_json_parse_array(ctx); + case OPA_JSON_TOKEN_OBJECT_START: + return opa_json_parse_object(ctx); + default: + return NULL; + } +} + +opa_value *opa_json_parse(const char *input, size_t len) +{ + opa_json_lex ctx; + opa_json_lex_init(input, len, &ctx); + int token = opa_json_lex_read(&ctx); + return opa_json_parse_token(&ctx, token); +} \ No newline at end of file diff --git a/wasm/src/json.h b/wasm/src/json.h new file mode 100644 index 0000000000..edf03d951b --- /dev/null +++ b/wasm/src/json.h @@ -0,0 +1,34 @@ +#ifndef OPA_JSON_H +#define OPA_JSON_H + +#include "value.h" + +typedef struct +{ + const char *input; + size_t len; + const char *buf; + const char *buf_end; + const char *curr; +} opa_json_lex; + +#define OPA_JSON_TOKEN_ERROR 0 +#define OPA_JSON_TOKEN_EOF 1 +#define OPA_JSON_TOKEN_NULL 2 +#define OPA_JSON_TOKEN_TRUE 3 +#define OPA_JSON_TOKEN_FALSE 4 +#define OPA_JSON_TOKEN_NUMBER 5 +#define OPA_JSON_TOKEN_STRING 6 +#define OPA_JSON_TOKEN_OBJECT_START 7 +#define OPA_JSON_TOKEN_OBJECT_END 8 +#define OPA_JSON_TOKEN_ARRAY_START 9 +#define OPA_JSON_TOKEN_ARRAY_END 10 +#define OPA_JSON_TOKEN_COMMA 11 +#define OPA_JSON_TOKEN_COLON 12 + +void opa_json_lex_init(const char *input, size_t len, opa_json_lex *ctx); +int opa_json_lex_read(opa_json_lex *ctx); + +opa_value *opa_json_parse(const char *input, size_t len); + +#endif \ No newline at end of file diff --git a/wasm/src/malloc.c b/wasm/src/malloc.c new file mode 100644 index 0000000000..2b9a234dce --- /dev/null +++ b/wasm/src/malloc.c @@ -0,0 +1,20 @@ +#include "std.h" + +#define WASM_PAGE_SIZE (65536) + +static unsigned int heap; + +static unsigned int grow_heap(size_t size) +{ + size_t pages = (size / WASM_PAGE_SIZE) + 1; + return __builtin_wasm_grow_memory(pages) * WASM_PAGE_SIZE; +} + +void *opa_malloc(size_t size) +{ + return (void *)grow_heap(size); +} + +void opa_free(void *ptr) +{ +} \ No newline at end of file diff --git a/wasm/src/malloc.h b/wasm/src/malloc.h new file mode 100644 index 0000000000..e2738f10dc --- /dev/null +++ b/wasm/src/malloc.h @@ -0,0 +1,9 @@ +#ifndef OPA_MALLOC_H +#define OPA_MALLOC_H + +#include "std.h" + +void *opa_malloc(size_t size); +void opa_free(void *ptr); + +#endif \ No newline at end of file diff --git a/wasm/src/std.h b/wasm/src/std.h new file mode 100644 index 0000000000..5f88ae08da --- /dev/null +++ b/wasm/src/std.h @@ -0,0 +1,19 @@ +#ifndef OPA_TYPES_H +#define OPA_TYPES_H + +#define NULL (0) +#define TRUE (1) +#define FALSE (0) + +typedef unsigned long size_t; + +#define offsetof(st, member) (size_t)(&((st *)0)->member) + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) ); }) + +void opa_abort(const char *msg); +void opa_println(const char *msg); + +#endif \ No newline at end of file diff --git a/wasm/src/string.c b/wasm/src/string.c new file mode 100644 index 0000000000..d0c32c68e2 --- /dev/null +++ b/wasm/src/string.c @@ -0,0 +1,90 @@ +#include "std.h" + +size_t opa_strlen(const char *s) +{ + const char *ptr = s; + + while (1) + { + if (*ptr == '\0') + { + return ptr - s; + } + + ptr += 1; + } +} + +int opa_strncmp(const char *a, const char *b, int num) +{ + unsigned char *a1 = (unsigned char *)a; + unsigned char *b1 = (unsigned char *)b; + + while (num--) + { + if (*a1 < *b1) + { + return -1; + } + else if (*a1 > *b1) + { + return 1; + } + a1++; + b1++; + } + + return 0; +} + +int opa_strcmp(const char *a, const char *b) +{ + size_t len_a = opa_strlen(a); + size_t len_b = opa_strlen(b); + size_t min = len_a; + + if (len_b < min) + { + min = len_b; + } + + unsigned char *a1 = (unsigned char *)a; + unsigned char *b1 = (unsigned char *)b; + + for (int i = 0; i < min; i++) + { + if (a1[i] < b1[i]) + { + return -1; + } + else if (a[i] > b[i]) + { + return 1; + } + } + + if (len_a < len_b) + { + return -1; + } + else if (len_a > len_b) + { + return 1; + } + return 0; +} + +int opa_isdigit(char b) +{ + return b >= '0' && b <= '9'; +} + +int opa_isspace(char b) +{ + return b == ' ' || b == '\r' || b == '\n' || b == '\t'; +} + +int opa_ishex(char b) +{ + return opa_isdigit(b) || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f'); +} \ No newline at end of file diff --git a/wasm/src/string.h b/wasm/src/string.h new file mode 100644 index 0000000000..4e3a10b6cf --- /dev/null +++ b/wasm/src/string.h @@ -0,0 +1,14 @@ +#ifndef OPA_STRING_H +#define OPA_STRING_H + +#include "std.h" + +size_t opa_strlen(const char *s); +double opa_strntod(const char *s, int len); +int opa_strncmp(const char *a, const char *b, int num); +int opa_strcmp(const char *a, const char *b); +int opa_isdigit(char b); +int opa_isspace(char b); +int opa_ishex(char b); + +#endif \ No newline at end of file diff --git a/wasm/src/undefined.symbols b/wasm/src/undefined.symbols new file mode 100644 index 0000000000..08a392b61b --- /dev/null +++ b/wasm/src/undefined.symbols @@ -0,0 +1,2 @@ +opa_abort +opa_println \ No newline at end of file diff --git a/wasm/src/value.c b/wasm/src/value.c new file mode 100644 index 0000000000..09ff357c7e --- /dev/null +++ b/wasm/src/value.c @@ -0,0 +1,689 @@ +#include "malloc.h" +#include "string.h" +#include "value.h" + +#define OPA_ARRAY_INITIAL_CAP (10) + +opa_value *opa_value_get_object(opa_object_t *obj, opa_value *key) +{ + opa_object_elem_t *elem = opa_object_get(obj, key); + + if (elem != NULL) + { + return elem->v; + } + + return NULL; +} + +opa_value *opa_value_get_array_native(opa_array_t *arr, long long i) +{ + if (i >= arr->len) + { + return NULL; + } + + return arr->elems[i].v; +} + +opa_value *opa_value_get_array(opa_array_t *arr, opa_value *key) +{ + if (key->type != OPA_NUMBER) + { + return NULL; + } + + opa_number_t *num = opa_cast_number(key); + + if (num->is_float) + { + return NULL; + } + + if (num->v.i < 0) + { + return NULL; + } + + return opa_value_get_array_native(arr, num->v.i); +} + +opa_value *opa_value_get(opa_value *node, opa_value *key) +{ + if (node != NULL) + { + switch (node->type) + { + case OPA_ARRAY: + return opa_value_get_array(opa_cast_array(node), key); + case OPA_OBJECT: + return opa_value_get_object(opa_cast_object(node), key); + } + } + return NULL; +} + +opa_value *opa_value_iter_object(opa_object_t *obj, opa_value *prev) +{ + if (prev == NULL) + { + if (obj->head == NULL) + { + return NULL; + } + + return obj->head->k; + } + + opa_object_elem_t *elem = opa_object_get(obj, prev); + + if (elem != NULL && elem->next != NULL) + { + return elem->next->k; + } + + return NULL; +} + +opa_value *opa_value_iter_array(opa_array_t *arr, opa_value *prev) +{ + if (prev == NULL) + { + if (arr->len == 0) + { + return NULL; + } + + return arr->elems[0].i; + } + + if (prev->type != OPA_NUMBER) + { + return NULL; + } + + opa_number_t *num = opa_cast_number(prev); + + if (num->is_float) + { + return NULL; + } + + long long i = num->v.i; + i++; + + if (i < 0 || i >= arr->len) + { + return NULL; + } + + return arr->elems[i].i; +} + +opa_value *opa_value_iter(opa_value *node, opa_value *prev) +{ + if (node != NULL) + { + switch (node->type) + { + case OPA_ARRAY: + return opa_value_iter_array(opa_cast_array(node), prev); + case OPA_OBJECT: + return opa_value_iter_object(opa_cast_object(node), prev); + } + } + + return NULL; +} + +size_t opa_value_length_object(opa_object_t *obj) +{ + size_t i = 0; + for (opa_object_elem_t *elem = obj->head; elem != NULL; elem = elem->next) + { + i++; + } + return i; +} + +size_t opa_value_length_array(opa_array_t *arr) +{ + return arr->len; +} + +size_t opa_value_length_string(opa_string_t *str) +{ + return str->len; +} + +size_t opa_value_length(opa_value *node) +{ + switch (node->type) + { + case OPA_ARRAY: + return opa_value_length_array(opa_cast_array(node)); + case OPA_OBJECT: + return opa_value_length_object(opa_cast_object(node)); + case OPA_STRING: + return opa_value_length_string(opa_cast_string(node)); + default: + return 0; + } +} + +int opa_value_compare_float(double a, double b) +{ + if (a < b) + { + return -1; + } + else if (a > b) + { + return 1; + } + return 0; +} + +int opa_value_compare_number(opa_number_t *a, opa_number_t *b) +{ + if (!a->is_float && !b->is_float) + { + if (a->v.i < b->v.i) + { + return -1; + } + else if (a->v.i > b->v.i) + { + return 1; + } + return 0; + } + + if (a->is_float && b->is_float) + { + return opa_value_compare_float(a->v.f, b->v.f); + } + + double a1, b1; + + if (!a->is_float) + { + a1 = (double)a->v.i; + b1 = b->v.f; + } + else + { + a1 = a->v.f; + b1 = (double)b->v.i; + } + + return opa_value_compare_float(a1, b1); +} + +int opa_value_compare_string(opa_string_t *a, opa_string_t *b) +{ + size_t min = a->len; + + if (b->len < min) + { + min = b->len; + } + + int cmp = opa_strncmp(a->v, b->v, min); + + if (cmp != 0) + { + return cmp; + } + + if (a->len < b->len) + { + return -1; + } + else if (a->len > b->len) + { + return 1; + } + return 0; +} + +int opa_value_compare_array(opa_array_t *a, opa_array_t *b) +{ + size_t a_len = opa_value_length_array(a); + size_t b_len = opa_value_length_array(b); + + size_t min = a_len; + + if (b_len < min) + { + min = b_len; + } + + for (long long i = 0; i < min; i++) + { + opa_value *e1 = opa_value_get_array_native(a, i); + opa_value *e2 = opa_value_get_array_native(b, i); + int cmp = opa_value_compare(e1, e2); + + if (cmp != 0) + { + return cmp; + } + } + + if (a_len < b_len) + { + return -1; + } + else if (a_len > b_len) + { + return 1; + } + return 0; +} + +int opa_value_compare_object(opa_object_t *a, opa_object_t *b) +{ + opa_array_t *a_keys = opa_object_keys(a); + opa_array_t *b_keys = opa_object_keys(b); + size_t a_len = opa_value_length_array(a_keys); + size_t b_len = opa_value_length_array(b_keys); + size_t min = a_len; + + if (b_len < min) + { + min = b_len; + } + + int cmp; + + for (size_t i = 0; i < min; i++) + { + cmp = opa_value_compare(a_keys->elems[i].v, b_keys->elems[i].v); + + if (cmp != 0) + { + goto finish; + } + + opa_value *a_val = opa_value_get_object(a, a_keys->elems[i].v); + opa_value *b_val = opa_value_get_object(b, b_keys->elems[i].v); + + cmp = opa_value_compare(a_val, b_val); + + if (cmp != 0) + { + goto finish; + } + } + + if (a_len < b_len) + { + return -1; + } + else if (a_len > b_len) + { + return 1; + } + +finish: + opa_array_free(a_keys); + opa_array_free(b_keys); + return cmp; +} + +int opa_value_compare(opa_value *a, opa_value *b) +{ + if (a == NULL && b == NULL) + { + return 0; + } + else if (b == NULL) + { + return 1; + } + else if (a == NULL) + { + return -1; + } + + if (a->type < b->type) + { + return -1; + } + else if (b->type < a->type) + { + return 1; + } + + switch (a->type) + { + case OPA_NULL: + return 0; + case OPA_BOOLEAN: + { + opa_boolean_t *a1 = opa_cast_boolean(a); + opa_boolean_t *b1 = opa_cast_boolean(b); + return a1->v - b1->v; + } + case OPA_NUMBER: + { + opa_number_t *a1 = opa_cast_number(a); + opa_number_t *b1 = opa_cast_number(b); + return opa_value_compare_number(a1, b1); + } + case OPA_STRING: + { + opa_string_t *a1 = opa_cast_string(a); + opa_string_t *b1 = opa_cast_string(b); + return opa_value_compare_string(a1, b1); + } + case OPA_ARRAY: + { + opa_array_t *a1 = opa_cast_array(a); + opa_array_t *b1 = opa_cast_array(b); + return opa_value_compare_array(a1, b1); + } + case OPA_OBJECT: + { + opa_object_t *a1 = opa_cast_object(a); + opa_object_t *b1 = opa_cast_object(b); + return opa_value_compare_object(a1, b1); + } + default: + { + opa_abort("illegal value"); + return 0; + } + } +} + +int opa_value_not_equal(opa_value *a, opa_value *b) +{ + return opa_value_compare(a, b) != 0; +} + +void opa_value_free(opa_value *node) +{ + switch (node->type) + { + case OPA_NULL: + opa_free(node); + return; + case OPA_BOOLEAN: + opa_free(opa_cast_boolean(node)); + return; + case OPA_NUMBER: + opa_free(opa_cast_number(node)); + return; + case OPA_STRING: + opa_free(opa_cast_string(node)); + return; + case OPA_ARRAY: + opa_array_free(opa_cast_array(node)); + return; + case OPA_OBJECT: + opa_object_free(opa_cast_object(node)); + return; + } +} + +int opa_value_boolean(opa_value *node) +{ + return opa_cast_boolean(node)->v; +} + +long long opa_value_int(opa_value *node) +{ + return opa_cast_number(node)->v.i; +} + +double opa_value_float(opa_value *node) +{ + return opa_cast_number(node)->v.f; +} + +const char *opa_value_string(opa_value *node) +{ + return opa_cast_string(node)->v; +} + +opa_value *opa_null() +{ + opa_value *ret = (opa_value *)opa_malloc(sizeof(opa_value)); + ret->type = OPA_NULL; + return ret; +} + +opa_value *opa_boolean(int v) +{ + opa_boolean_t *ret = (opa_boolean_t *)opa_malloc(sizeof(opa_boolean_t)); + ret->hdr.type = OPA_BOOLEAN; + ret->v = v; + return &ret->hdr; +} + +opa_value *opa_number_int(long long v) +{ + opa_number_t *ret = (opa_number_t *)opa_malloc(sizeof(opa_number_t)); + ret->hdr.type = OPA_NUMBER; + ret->is_float = 0; + ret->v.i = v; + return &ret->hdr; +} + +opa_value *opa_number_float(double v) +{ + opa_number_t *ret = (opa_number_t *)opa_malloc(sizeof(opa_number_t)); + ret->hdr.type = OPA_NUMBER; + ret->is_float = 1; + ret->v.f = v; + return &ret->hdr; +} + +opa_value *opa_string(const char *v, size_t len) +{ + opa_string_t *ret = (opa_string_t *)opa_malloc(sizeof(opa_string_t)); + ret->hdr.type = OPA_STRING; + ret->len = len; + ret->v = v; + return &ret->hdr; +} + +opa_value *opa_string_terminated(const char *v) +{ + opa_string_t *ret = (opa_string_t *)opa_malloc(sizeof(opa_string_t)); + ret->hdr.type = OPA_STRING; + ret->len = opa_strlen(v); + ret->v = v; + return &ret->hdr; +} + +void __opa_array_grow(opa_array_t *arr) +{ + if (arr->cap == 0) + { + arr->cap = OPA_ARRAY_INITIAL_CAP; + } + else + { + arr->cap *= 2; + } + + opa_array_elem_t *elems = (opa_array_elem_t *)opa_malloc(arr->cap * sizeof(opa_array_elem_t)); + + for (int i = 0; i < arr->len; i++) + { + elems[i] = arr->elems[i]; + } + + opa_free(arr->elems); + arr->elems = elems; +} + +opa_value *opa_array() +{ + return opa_array_with_cap(0); +} + +opa_value *opa_array_with_cap(size_t cap) +{ + opa_array_t *ret = (opa_array_t *)opa_malloc(sizeof(opa_array_t)); + ret->hdr.type = OPA_ARRAY; + ret->len = 0; + ret->cap = cap; + ret->elems = NULL; + + if (ret->cap != 0) + { + __opa_array_grow(ret); + } + + return &ret->hdr; +} + +opa_value *opa_object() +{ + opa_object_t *ret = (opa_object_t *)opa_malloc(sizeof(opa_object_t)); + ret->hdr.type = OPA_OBJECT; + return &ret->hdr; +} + +void opa_array_free(opa_array_t *arr) +{ + if (arr->elems != NULL) + { + for (size_t i = 0; i < arr->len; i++) + { + opa_free(arr->elems[i].i); + } + + opa_free(arr->elems); + } + + opa_free(arr); +} + +void opa_array_append(opa_array_t *arr, opa_value *v) +{ + if (arr->len >= arr->cap) + { + __opa_array_grow(arr); + } + + size_t i = arr->len++; + arr->elems[i].i = opa_number_int(i); + arr->elems[i].v = v; +} + +void opa_array_sort(opa_array_t *arr, opa_compare_fn cmp_fn) +{ + for (size_t i = 1; i < arr->len; i++) + { + opa_array_elem_t elem = arr->elems[i]; + size_t j = i - 1; + + while (j >= 0 && cmp_fn(arr->elems[j].v, elem.v) > 0) + { + arr->elems[j + 1] = arr->elems[j]; + j = j - 1; + } + + arr->elems[j + 1] = elem; + } +} + +void opa_object_free(opa_object_t *obj) +{ + opa_object_elem_t *prev = NULL; + + for (opa_object_elem_t *curr = obj->head; curr != NULL; curr = curr->next) + { + if (prev != NULL) + { + opa_free(prev); + } + + prev = curr; + } + + if (prev != NULL) + { + opa_free(prev); + } +} + +opa_array_t *opa_object_keys(opa_object_t *obj) +{ + opa_array_t *ret = opa_cast_array(opa_array_with_cap(opa_value_length_object(obj))); + opa_object_elem_t *elem = opa_object_iter(obj, NULL); + + while (elem != NULL) + { + opa_array_append(ret, elem->k); + elem = opa_object_iter(obj, elem); + } + + opa_array_sort(ret, opa_value_compare); + return ret; +} + +opa_object_elem_t *__opa_object_elem_alloc(opa_value *k, opa_value *v) +{ + opa_object_elem_t *elem = (opa_object_elem_t *)opa_malloc(sizeof(opa_object_elem_t)); + elem->next = NULL; + elem->k = k; + elem->v = v; + return elem; +} + +void opa_object_insert(opa_object_t *obj, opa_value *k, opa_value *v) +{ + opa_object_elem_t *curr = NULL; + + if (obj->head != NULL) + { + for (curr = obj->head; curr->next != NULL; curr = curr->next) + { + if (opa_value_compare(curr->k, k) == 0) + { + curr->v = v; + return; + } + } + } + + opa_object_elem_t *new = __opa_object_elem_alloc(k, v); + + if (curr != NULL) + { + curr->next = new; + } + else + { + obj->head = new; + } +} + +opa_object_elem_t *opa_object_get(opa_object_t *obj, opa_value *key) +{ + for (opa_object_elem_t *curr = obj->head; curr != NULL; curr = curr->next) + { + if (opa_value_compare(curr->k, key) == 0) + { + return curr; + } + } + + return NULL; +} + +opa_object_elem_t *opa_object_iter(opa_object_t *obj, opa_object_elem_t *prev) +{ + if (prev == NULL) + { + return obj->head; + } + + return prev->next; +} diff --git a/wasm/src/value.h b/wasm/src/value.h new file mode 100644 index 0000000000..fedda8ef2d --- /dev/null +++ b/wasm/src/value.h @@ -0,0 +1,115 @@ +#ifndef OPA_VALUE_H +#define OPA_VALUE_H + +#include "std.h" + +#define OPA_NULL (1) +#define OPA_BOOLEAN (2) +#define OPA_NUMBER (3) +#define OPA_STRING (4) +#define OPA_ARRAY (5) +#define OPA_OBJECT (6) + +#define OPA_EXT_INT (1) +#define OPA_EXT_FLOAT (2) + +typedef struct opa_value opa_value; + +struct opa_value +{ + unsigned char type; +}; + +typedef struct +{ + opa_value hdr; + int v; +} opa_boolean_t; + +typedef struct +{ + opa_value hdr; + unsigned char is_float; + union { + long long i; + double f; + } v; +} opa_number_t; + +typedef struct +{ + opa_value hdr; + size_t len; + const char *v; +} opa_string_t; + +typedef struct +{ + opa_value *i; + opa_value *v; +} opa_array_elem_t; + +typedef struct +{ + opa_value hdr; + opa_array_elem_t *elems; + size_t len; + size_t cap; +} opa_array_t; + +typedef struct opa_object_elem_t opa_object_elem_t; + +struct opa_object_elem_t +{ + opa_value *k; + opa_value *v; + opa_object_elem_t *next; +}; + +typedef struct +{ + opa_value hdr; + opa_object_elem_t *head; +} opa_object_t; + +typedef int (*opa_compare_fn)(opa_value *, opa_value *t); + +#define opa_cast_boolean(v) container_of(v, opa_boolean_t, hdr) +#define opa_cast_number(v) container_of(v, opa_number_t, hdr) +#define opa_cast_string(v) container_of(v, opa_string_t, hdr) +#define opa_cast_array(v) container_of(v, opa_array_t, hdr) +#define opa_cast_object(v) container_of(v, opa_object_t, hdr) + +int opa_value_compare(opa_value *a, opa_value *b); +int opa_value_not_equal(opa_value *a, opa_value *b); +opa_value *opa_value_get(opa_value *node, opa_value *key); +opa_value *opa_value_iter(opa_value *node, opa_value *prev); +size_t opa_value_length(opa_value *node); +void opa_value_free(opa_value *node); + +long long opa_value_int(opa_value *node); +double opa_value_float(opa_value *node); +const char *opa_value_string(opa_value *node); +int opa_value_boolean(opa_value *node); + +opa_value *opa_null(); +opa_value *opa_boolean(int v); +opa_value *opa_number_int(long long v); +opa_value *opa_number_float(double v); +opa_value *opa_string(const char *v, size_t len); +opa_value *opa_string_terminated(const char *v); +opa_value *opa_array(); +opa_value *opa_array_with_cap(size_t cap); +opa_value *opa_object(); + +void opa_array_free(opa_array_t *arr); +void opa_array_append(opa_array_t *arr, opa_value *v); +void opa_array_sort(opa_array_t *arr, opa_compare_fn cmp_fn); + +void opa_object_free(opa_object_t *obj); +opa_array_t *opa_object_keys(opa_object_t *obj); +void opa_object_insert(opa_object_t *obj, opa_value *k, opa_value *v); +opa_object_elem_t *opa_object_get(opa_object_t *obj, opa_value *key); +opa_object_elem_t *opa_object_iter(opa_object_t *obj, opa_object_elem_t *prev); + +#endif \ No newline at end of file diff --git a/wasm/test.js b/wasm/test.js new file mode 100644 index 0000000000..2691da063b --- /dev/null +++ b/wasm/test.js @@ -0,0 +1,113 @@ +const { readFileSync } = require('fs'); + +function stringDecoder(mem) { + return function(addr) { + const i8 = new Int8Array(mem.buffer); + const start = addr; + var s = ""; + while (i8[addr] != 0) { + s += String.fromCharCode(i8[addr++]); + } + return s; + } +} + +function red(text) { + return '\x1b[0m\x1b[31m' + text + '\x1b[0m'; +} + +function green(text) { + return '\x1b[0m\x1b[32m' + text + '\x1b[0m'; +} + +function yellow(text) { + return '\x1b[0m\x1b[33m' + text + '\x1b[0m'; +} + +function namespace(cache, func, note) +{ + let key = func+note; + if (key in cache) { + cache[key] += 1; + note = note + ' (' + cache[key] + ')' + } else { + cache[key] = 0; + } + return note; +} + +function report(passed, error, msg) { + if (passed === true) { + if (process.env.VERBOSE === '1') { + console.log(green('PASS'), msg); + } + } else if (error === undefined) { + console.log(yellow('FAIL'), msg); + } else { + console.log(red('ERROR'), msg, error); + } +} + +async function test(executable) { + + const mem = new WebAssembly.Memory({initial: 5}); + const addr2string = stringDecoder(mem); + + let cache = {}; + let failedOrErrored = 0; + let seenFuncs = {}; + + const module = await WebAssembly.instantiate(readFileSync(executable), { + env: { + memory: mem, + opa_println: (msg) => { + console.log(addr2string(msg)); + }, + opa_abort: (msg) => { + throw 'abort: ' + addr2string(msg); + }, + opa_test_pass: (note, func) => { + note = addr2string(note); + func = addr2string(func); + note = namespace(cache, func, note); + seenFuncs[func] = true; + let key = func + '/' + note + report(true, undefined, key); + }, + opa_test_fail: (note, func, file, line) => { + note = addr2string(note); + func = addr2string(func); + note = namespace(cache, func, note); + seenFuncs[func] = true; + let key = func + '/' + note; + failedOrErrored++; + report(false, undefined, key + ' ' + addr2string(file) + ':' + line); + }, + } + }); + + for (let key in module.instance.exports) { + if (key.startsWith("test_")) { + try { + module.instance.exports[key](); + if (!(key in seenFuncs)) + { + report(true, undefined, key); + } + } catch(e) { + report(false, e, key) + } + } + } + + if (failedOrErrored > 0) { + process.exit(1); + } +} + +if (process.argv.length != 3) { + console.log(process.argv[1] + " "); + process.exit(1); +} + +test(process.argv[2]); diff --git a/wasm/tests/test.c b/wasm/tests/test.c new file mode 100644 index 0000000000..7baa348d3c --- /dev/null +++ b/wasm/tests/test.c @@ -0,0 +1,493 @@ +#include "string.h" +#include "json.h" + +void opa_test_fail(const char *note, const char *func, const char *file, int line); +void opa_test_pass(const char *note, const char *func); + +#define test_fatal(note) \ + { \ + opa_test_fail(note, __func__, __FILE__, __LINE__); \ + return; \ + } + +#define test(note, expr) \ + if (!(expr)) \ + { \ + opa_test_fail(note, __func__, __FILE__, __LINE__); \ + } \ + else \ + { \ + opa_test_pass(note, __func__); \ + } + +void test_opa_strlen() +{ + test("empty", opa_strlen("") == 0); + test("non-empty", opa_strlen("1234") == 4); +} + +void test_opa_strncmp() +{ + test("empty", opa_strncmp("", "", 0) == 0); + test("equal", opa_strncmp("1234", "1234", 4) == 0); + test("less than", opa_strncmp("1234", "1243", 4) < 0); + test("greater than", opa_strncmp("1243", "1234", 4) > 0); +} + +void test_opa_strcmp() +{ + test("empty", opa_strcmp("", "") == 0); + test("equal", opa_strcmp("abcd", "abcd") == 0); + test("less than", opa_strcmp("1234", "1243") < 0); + test("greater than", opa_strcmp("1243", "1234") > 0); + test("shorter", opa_strcmp("123", "1234") < 0); + test("longer", opa_strcmp("1234", "123") > 0); +} + +int lex_crunch(const char *s) +{ + opa_json_lex ctx; + opa_json_lex_init(s, opa_strlen(s), &ctx); + return opa_json_lex_read(&ctx); +} + +void test_opa_lex_tokens() +{ + test("empty", lex_crunch("") == OPA_JSON_TOKEN_EOF); + test("space", lex_crunch(" ") == OPA_JSON_TOKEN_EOF); + test("tab", lex_crunch("\t") == OPA_JSON_TOKEN_EOF); + test("newline", lex_crunch("\n") == OPA_JSON_TOKEN_EOF); + test("carriage return", lex_crunch("\r") == OPA_JSON_TOKEN_EOF); + test("null", lex_crunch("null") == OPA_JSON_TOKEN_NULL); + test("true", lex_crunch("true") == OPA_JSON_TOKEN_TRUE); + test("false", lex_crunch("false") == OPA_JSON_TOKEN_FALSE); + + test("bad unicode", lex_crunch("\" \\uabcx \"") == OPA_JSON_TOKEN_ERROR); // not hex + test("object start", lex_crunch(" { ") == OPA_JSON_TOKEN_OBJECT_START); + test("object end", lex_crunch(" } ") == OPA_JSON_TOKEN_OBJECT_END); + test("array start", lex_crunch(" [ ") == OPA_JSON_TOKEN_ARRAY_START); + test("array end", lex_crunch(" ] ") == OPA_JSON_TOKEN_ARRAY_END); + test("element separator", lex_crunch(" , ") == OPA_JSON_TOKEN_COMMA); + test("item separator", lex_crunch(" : ") == OPA_JSON_TOKEN_COLON); +} + +int lex_buffer_crunch(const char *s, const char *exp, int token) +{ + opa_json_lex ctx; + opa_json_lex_init(s, opa_strlen(s), &ctx); + + if (opa_json_lex_read(&ctx) != token) + { + return -1; + } + + size_t exp_len = opa_strlen(exp); + size_t buf_len = ctx.buf_end - ctx.buf; + + if (exp_len != buf_len) + { + return -2; + } + + if (opa_strncmp(ctx.buf, exp, buf_len) != 0) + { + return -3; + } + + return 0; +} + +#define test_lex_buffer(note, s, exp, token) test(note, (lex_buffer_crunch(s, exp, token) == 0)) + +void test_opa_lex_buffer() +{ + test_lex_buffer("zero", "0", "0", OPA_JSON_TOKEN_NUMBER); + test_lex_buffer("signed zero", "-0", "-0", OPA_JSON_TOKEN_NUMBER); + test_lex_buffer("integers", "1234567890", "1234567890", OPA_JSON_TOKEN_NUMBER); + test_lex_buffer("signed integers", "-1234567890", "-1234567890", OPA_JSON_TOKEN_NUMBER); + test_lex_buffer("floats", "0.1234567890", "0.1234567890", OPA_JSON_TOKEN_NUMBER); + test_lex_buffer("signed floats", "-0.1234567890", "-0.1234567890", OPA_JSON_TOKEN_NUMBER); + test_lex_buffer("exponents", "-0.1234567890e0", "-0.1234567890e0", OPA_JSON_TOKEN_NUMBER); + test_lex_buffer("exponents", "-0.1234567890E+1000", "-0.1234567890E+1000", OPA_JSON_TOKEN_NUMBER); + test_lex_buffer("exponents", "-0.1234567890E-1000", "-0.1234567890E-1000", OPA_JSON_TOKEN_NUMBER); + test_lex_buffer("empty string", "\"\"", "", OPA_JSON_TOKEN_STRING); + test_lex_buffer("escaped quote", "\"\\\"\"", "\\\"", OPA_JSON_TOKEN_STRING); + test_lex_buffer("escaped reverse solidus", "\"\\\\\"", "\\\\", OPA_JSON_TOKEN_STRING); + test_lex_buffer("escaped solidus", "\"\\/\"", "\\/", OPA_JSON_TOKEN_STRING); + test_lex_buffer("escaped backspace", "\"\\b\"", "\\b", OPA_JSON_TOKEN_STRING); + test_lex_buffer("escaped feed forward", "\"\\f\"", "\\f", OPA_JSON_TOKEN_STRING); + test_lex_buffer("escaped line feed", "\"\\n\"", "\\n", OPA_JSON_TOKEN_STRING); + test_lex_buffer("escaped carriage return", "\"\\r\"", "\\r", OPA_JSON_TOKEN_STRING); + test_lex_buffer("escaped tab", "\"\\t\"", "\\t", OPA_JSON_TOKEN_STRING); + test_lex_buffer("plain", "\"abcdefg\"", "abcdefg", OPA_JSON_TOKEN_STRING); +} + +void test_opa_value_compare() +{ + test("none", opa_value_compare(NULL, NULL) == 0); + test("none/some", opa_value_compare(NULL, opa_null()) < 0); + test("some/none", opa_value_compare(opa_null(), NULL) > 0); + test("null", opa_value_compare(opa_null(), opa_null()) == 0); + test("null/boolean", opa_value_compare(opa_boolean(TRUE), opa_null()) > 0); + test("true/true", opa_value_compare(opa_boolean(TRUE), opa_boolean(TRUE)) == 0); + test("true/false", opa_value_compare(opa_boolean(TRUE), opa_boolean(FALSE)) > 0); + test("false/true", opa_value_compare(opa_boolean(FALSE), opa_boolean(TRUE)) < 0); + test("false/false", opa_value_compare(opa_boolean(FALSE), opa_boolean(FALSE)) == 0); + test("number/boolean", opa_value_compare(opa_number_int(100), opa_boolean(TRUE)) > 0); + test("integers", opa_value_compare(opa_number_int(100), opa_number_int(99)) > 0); + test("integers", opa_value_compare(opa_number_int(100), opa_number_int(101)) < 0); + test("integers", opa_value_compare(opa_number_int(100), opa_number_int(100)) == 0); + test("integers", opa_value_compare(opa_number_int(-100), opa_number_int(100)) < 0); + test("integers", opa_value_compare(opa_number_int(-100), opa_number_int(-101)) > 0); + test("integer/float", opa_value_compare(opa_number_int(100), opa_number_float(100.1)) < 0); + test("floats", opa_value_compare(opa_number_float(100.2), opa_number_float(100.1)) > 0); + test("floats", opa_value_compare(opa_number_float(100.2), opa_number_float(100.3)) < 0); + test("floats", opa_value_compare(opa_number_float(100.3), opa_number_float(100.3)) == 0); + test("string/number", opa_value_compare(opa_string_terminated("foo"), opa_number_float(100)) > 0); + test("strings", opa_value_compare(opa_string_terminated("foo"), opa_string_terminated("foo")) == 0); + test("strings", opa_value_compare(opa_string_terminated("foo"), opa_string_terminated("bar")) > 0); + test("strings", opa_value_compare(opa_string_terminated("bar"), opa_string_terminated("baz")) < 0); + test("strings", opa_value_compare(opa_string_terminated("foobar"), opa_string_terminated("foo")) > 0); + test("strings", opa_value_compare(opa_string_terminated("foo"), opa_string_terminated("foobar")) < 0); + + opa_array_t *arr1 = opa_cast_array(opa_array()); + opa_array_append(arr1, opa_number_int(1)); + opa_array_append(arr1, opa_number_int(2)); + opa_array_append(arr1, opa_number_int(3)); + + opa_array_t *arr2 = opa_cast_array(opa_array()); + opa_array_append(arr2, opa_number_int(1)); + opa_array_append(arr2, opa_number_int(3)); + opa_array_append(arr2, opa_number_int(2)); + + opa_array_t *arr3 = opa_cast_array(opa_array()); + opa_array_append(arr2, opa_number_int(1)); + opa_array_append(arr2, opa_number_int(3)); + + opa_value *v1, *v2, *v3; + v1 = &arr1->hdr; + v2 = &arr2->hdr; + v3 = &arr3->hdr; + + test("array/string", opa_value_compare(v1, opa_string_terminated("a")) > 0); + test("arrays", opa_value_compare(v1, v1) == 0); + test("arrays", opa_value_compare(v1, v2) < 0); + test("arrays", opa_value_compare(v2, v1) > 0); + test("arrays", opa_value_compare(v3, v2) < 0); + test("arrays", opa_value_compare(v2, v3) > 0); + + opa_object_t *obj1 = opa_cast_object(opa_object()); + opa_object_insert(obj1, opa_string_terminated("a"), opa_number_int(1)); + opa_object_insert(obj1, opa_string_terminated("b"), opa_number_int(2)); + + opa_object_t *obj2 = opa_cast_object(opa_object()); + opa_object_insert(obj2, opa_string_terminated("a"), opa_number_int(1)); + opa_object_insert(obj2, opa_string_terminated("b"), opa_number_int(3)); + + opa_object_t *obj3 = opa_cast_object(opa_object()); + opa_object_insert(obj3, opa_string_terminated("a"), opa_number_int(1)); + opa_object_insert(obj3, opa_string_terminated("c"), opa_number_int(3)); + + opa_object_t *obj4 = opa_cast_object(opa_object()); + opa_object_insert(obj4, opa_string_terminated("a"), opa_number_int(1)); + opa_object_insert(obj4, opa_string_terminated("b"), opa_number_int(2)); + opa_object_insert(obj4, opa_string_terminated("c"), opa_number_int(3)); + + v1 = &obj1->hdr; + v2 = &obj2->hdr; + v3 = &obj3->hdr; + opa_value *v4 = &obj4->hdr; + + test("object/array", opa_value_compare(v1, opa_array()) > 0); + test("objects", opa_value_compare(v1, v1) == 0); + test("objects", opa_value_compare(v1, v2) < 0); + test("objects", opa_value_compare(v2, v3) < 0); + test("objects", opa_value_compare(v4, v1) > 0); + test("objects", opa_value_compare(v4, v2) < 0); +} + +int parse_crunch(const char *s, opa_value *exp) +{ + opa_value *ret = opa_json_parse(s, opa_strlen(s)); + if (ret == NULL) + { + return 0; + } + return opa_value_compare(exp, ret) == 0; +} + +void test_opa_json_parse_scalar() +{ + test("null", parse_crunch("null", opa_null())); + test("true", parse_crunch("true", opa_boolean(TRUE))); + test("false", parse_crunch("false", opa_boolean(FALSE))); + test("strings", parse_crunch("\"hello\"", opa_string_terminated("hello"))); + test("integers", parse_crunch("0", opa_number_int(0))); + test("integers", parse_crunch("123456789", opa_number_int(123456789))); + test("signed integers", parse_crunch("-0", opa_number_int(0))); + test("signed integers", parse_crunch("-123456789", opa_number_int(-123456789))); + test("floats", parse_crunch("16.7", opa_number_float(16.7))); + test("signed floats", parse_crunch("-16.7", opa_number_float(-16.7))); + test("exponents", parse_crunch("6e7", opa_number_float(6e7))); +} + +opa_array_t *fixture_array1() +{ + opa_array_t *arr = opa_cast_array(opa_array()); + opa_array_append(arr, opa_number_int(1)); + opa_array_append(arr, opa_number_int(2)); + opa_array_append(arr, opa_number_int(3)); + opa_array_append(arr, opa_number_int(4)); + return arr; +} + +opa_array_t *fixture_array2() +{ + opa_array_t *arr1 = opa_cast_array(opa_array()); + opa_array_append(arr1, opa_number_int(1)); + opa_array_append(arr1, opa_number_int(2)); + opa_array_append(arr1, opa_number_int(3)); + opa_array_append(arr1, opa_number_int(4)); + + opa_array_t *arr2 = opa_cast_array(opa_array()); + opa_array_append(arr2, opa_number_int(5)); + opa_array_append(arr2, opa_number_int(6)); + opa_array_append(arr2, opa_number_int(7)); + opa_array_append(arr2, opa_number_int(8)); + + opa_array_t *arr = opa_cast_array(opa_array()); + opa_array_append(arr, &arr1->hdr); + opa_array_append(arr, &arr2->hdr); + + return arr; +} + +opa_object_t *fixture_object1() +{ + opa_object_t *obj = opa_cast_object(opa_object()); + opa_object_insert(obj, opa_string_terminated("a"), opa_number_int(1)); + opa_object_insert(obj, opa_string_terminated("b"), opa_number_int(2)); + return obj; +} + +opa_object_t *fixture_object2() +{ + opa_object_t *obj1 = opa_cast_object(opa_object()); + opa_object_insert(obj1, opa_string_terminated("c"), opa_number_int(1)); + opa_object_insert(obj1, opa_string_terminated("d"), opa_number_int(2)); + + opa_object_t *obj2 = opa_cast_object(opa_object()); + opa_object_insert(obj2, opa_string_terminated("e"), opa_number_int(3)); + opa_object_insert(obj2, opa_string_terminated("f"), opa_number_int(4)); + + opa_object_t *obj = opa_cast_object(opa_object()); + opa_object_insert(obj, opa_string_terminated("a"), &obj1->hdr); + opa_object_insert(obj, opa_string_terminated("b"), &obj2->hdr); + return obj; +} + +void test_opa_value_length() +{ + opa_array_t *arr = fixture_array1(); + opa_object_t *obj = fixture_object1(); + + test("arrays", opa_value_length(&arr->hdr) == 4); + test("objects", opa_value_length(&obj->hdr) == 2); +} + +void test_opa_value_get_array() +{ + opa_array_t *arr = fixture_array1(); + + for (int i = 0; i < 4; i++) + { + opa_value *result = opa_value_get(&arr->hdr, opa_number_int(i)); + + if (result == NULL) + { + test_fatal("array get failed"); + } + + if (opa_value_compare(result, opa_number_int(i + 1)) != 0) + { + test_fatal("array get returned bad value"); + } + } + + opa_value *result = opa_value_get(&arr->hdr, opa_string_terminated("foo")); + + if (result != NULL) + { + test_fatal("array get returned unexpected result"); + } + + result = opa_value_get(&arr->hdr, opa_number_float(3.14)); + + if (result != NULL) + { + test_fatal("array get returned unexpected result"); + } + + result = opa_value_get(&arr->hdr, opa_number_int(-1)); + + if (result != NULL) + { + test_fatal("array get returned unexpected result"); + } + + result = opa_value_get(&arr->hdr, opa_number_int(4)); + + if (result != NULL) + { + test_fatal("array get returned unexpected result"); + } +} + +void test_opa_array_sort() +{ + opa_array_t *arr = opa_cast_array(opa_array()); + + opa_array_append(arr, opa_number_int(4)); + opa_array_append(arr, opa_number_int(3)); + opa_array_append(arr, opa_number_int(2)); + opa_array_append(arr, opa_number_int(1)); + + opa_array_sort(arr, opa_value_compare); + + opa_value *res = &arr->hdr; + opa_value *exp = &fixture_array1()->hdr; + + if (opa_value_compare(res, exp) != 0) + { + test_fatal("array sort returned unexpected result"); + } +} + +void test_opa_value_get_object() +{ + opa_object_t *obj = fixture_object1(); + + const char *keys[2] = { + "a", + "b", + }; + + long long values[2] = { + 1, + 2, + }; + + for (int i = 0; i < sizeof(keys) / sizeof(const char *); i++) + { + opa_value *result = opa_value_get(&obj->hdr, opa_string_terminated(keys[i])); + + if (result == NULL) + { + test_fatal("object get failed"); + } + + if (opa_value_compare(result, opa_number_int(values[i])) != 0) + { + test_fatal("object get returned bad value"); + } + } + + opa_value *result = opa_value_get(&obj->hdr, opa_string_terminated("non-existent")); + + if (result != NULL) + { + test_fatal("object get returned unexpected result"); + } +} + +void test_opa_json_parse_composites() +{ + + opa_value *empty_arr = opa_array(); + + test("empty array", parse_crunch("[]", empty_arr)); + test("array", parse_crunch("[1,2,3,4]", &fixture_array1()->hdr)); + test("array nested", parse_crunch("[[1,2,3,4],[5,6,7,8]]", &fixture_array2()->hdr)); + + opa_value *empty_obj = opa_object(); + + test("empty object", parse_crunch("{}", empty_obj)); + test("object", parse_crunch("{\"a\": 1, \"b\": 2}", &fixture_object1()->hdr)); + test("object nested", parse_crunch("{\"a\": {\"c\": 1, \"d\": 2}, \"b\": {\"e\": 3, \"f\": 4}}", &fixture_object2()->hdr)); +} + +void test_opa_object_insert() +{ + + opa_object_t *obj = opa_cast_object(opa_object()); + + opa_object_insert(obj, opa_string_terminated("a"), opa_number_int(1)); + opa_object_insert(obj, opa_string_terminated("b"), opa_number_int(2)); + opa_object_insert(obj, opa_string_terminated("a"), opa_number_int(3)); + + opa_value *v1 = opa_value_get(&obj->hdr, opa_string_terminated("a")); + + if (opa_value_compare(v1, opa_number_int(3)) != 0) + { + test_fatal("object insert did not replace value") + } +} + +void test_opa_value_iter_object() +{ + opa_object_t *obj = fixture_object1(); + + opa_value *k1 = opa_value_iter(&obj->hdr, NULL); + opa_value *k2 = opa_value_iter(&obj->hdr, k1); + opa_value *k3 = opa_value_iter(&obj->hdr, k2); + + opa_value *exp1 = opa_string_terminated("a"); + opa_value *exp2 = opa_string_terminated("b"); + opa_value *exp3 = NULL; + + if (opa_value_not_equal(k1, exp1)) + { + test_fatal("object iter start did not return expected value"); + } + + if (opa_value_not_equal(k2, exp2)) + { + test_fatal("object iter second did not return expected value"); + } + + if (opa_value_not_equal(k3, exp3)) + { + test_fatal("object iter third did not return expected value"); + } +} + +void test_opa_value_iter_array() +{ + opa_array_t *arr = opa_cast_array(opa_array()); + + opa_array_append(arr, opa_number_int(1)); + opa_array_append(arr, opa_number_int(2)); + + opa_value *k1 = opa_value_iter(&arr->hdr, NULL); + opa_value *k2 = opa_value_iter(&arr->hdr, k1); + opa_value *k3 = opa_value_iter(&arr->hdr, k2); + + opa_value *exp1 = opa_number_int(0); + opa_value *exp2 = opa_number_int(1); + opa_value *exp3 = NULL; + + if (opa_value_not_equal(k1, exp1)) + { + test_fatal("array iter start did not return expected value"); + } + + if (opa_value_not_equal(k2, exp2)) + { + test_fatal("array iter second did not return expected value"); + } + + if (opa_value_not_equal(k3, exp3)) + { + test_fatal("array iter third did not return expected value"); + } +} diff --git a/wasm/tests/undefined.symbols b/wasm/tests/undefined.symbols new file mode 100644 index 0000000000..506ba2c60d --- /dev/null +++ b/wasm/tests/undefined.symbols @@ -0,0 +1,4 @@ +opa_test_pass +opa_test_fail +opa_abort +opa_println \ No newline at end of file