diff --git a/backend-c/Dockerfile b/backend-c/Dockerfile new file mode 100644 index 0000000..a5678d6 --- /dev/null +++ b/backend-c/Dockerfile @@ -0,0 +1,14 @@ +FROM ubuntu:19.04 + +RUN apt-get update \ + && apt-get install -y ca-certificates \ + curl \ + git \ + make \ + libtinfo5 + +RUN curl -L https://github.com/CraneStation/wasi-sdk/releases/download/wasi-sdk-6/wasi-sdk-6.0-linux.tar.gz | tar xz --strip-components=1 -C / + +VOLUME /code +WORKDIR /code +CMD make diff --git a/backend-c/LICENSE b/backend-c/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/backend-c/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/backend-c/Makefile b/backend-c/Makefile new file mode 100644 index 0000000..0905740 --- /dev/null +++ b/backend-c/Makefile @@ -0,0 +1,23 @@ +TARGET = hello_world +CC = /opt/wasi-sdk/bin/clang +SYSROOT = /opt/wasi-sdk/share/wasi-sysroot +TARGET_TRIPLE = wasm32-unknown-wasi +CFLAGS = -nostartfiles -fvisibility=hidden +LDFLAGS = -Wl,--no-entry,--demangle,--allow-undefined +EXPORT_FUNCS = --export=allocate,--export=deallocate,--export=invoke +SDK = sdk/allocator.c sdk/logger.c +SRC = src/main.c src/model.c +LIBS = libs/tiny-json/tiny-json.c + +.PHONY: default all clean + +default: $(TARGET) +all: default + +$(TARGET): $(SRC) $(SDK) $(LIBS) + $(CC) --sysroot=$(SYSROOT) --target=$(TARGET_TRIPLE) $(CFLAGS) $(LDFLAGS) -Wl,$(EXPORT_FUNCS) $^ -o $@.wasm + +.PRECIOUS: $(TARGET) + +clean: + -rm -f $(TARGET).wasm diff --git a/backend-c/README.md b/backend-c/README.md new file mode 100644 index 0000000..53bd1ff --- /dev/null +++ b/backend-c/README.md @@ -0,0 +1,16 @@ +# Fluid + +The twitter-like application for Fluence. + +# How to build + +This app could be built either with docker + +```bash +docker-compose up +``` + +or by Makefile with [wasi-sdk](https://github.com/CraneStation/wasi-sdk) installed +```bash +make +``` diff --git a/backend-c/docker-compose.yml b/backend-c/docker-compose.yml new file mode 100644 index 0000000..42ef770 --- /dev/null +++ b/backend-c/docker-compose.yml @@ -0,0 +1,7 @@ +version: '3' +services: + hello_world: + build: + context: . + volumes: + - .:/code diff --git a/backend-c/libs/tiny-json/.travis.yml b/backend-c/libs/tiny-json/.travis.yml new file mode 100644 index 0000000..cdd74dc --- /dev/null +++ b/backend-c/libs/tiny-json/.travis.yml @@ -0,0 +1,5 @@ +language: c +sudo: false +script: + - cd test + - make test diff --git a/backend-c/libs/tiny-json/tiny-json.c b/backend-c/libs/tiny-json/tiny-json.c new file mode 100644 index 0000000..b856690 --- /dev/null +++ b/backend-c/libs/tiny-json/tiny-json.c @@ -0,0 +1,463 @@ + +/* + + + + Licensed under the MIT License . + SPDX-License-Identifier: MIT + Copyright (c) 2016-2018 Rafa Garcia . + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ + +#include +#include +#include "tiny-json.h" + +/** Structure to handle a heap of JSON properties. */ +typedef struct jsonStaticPool_s { + json_t* const mem; /**< Pointer to array of json properties. */ + unsigned int const qty; /**< Length of the array of json properties. */ + unsigned int nextFree; /**< The index of the next free json property. */ + jsonPool_t pool; +} jsonStaticPool_t; + +/* Search a property by its name in a JSON object. */ +json_t const* json_getProperty( json_t const* obj, char const* property ) { + json_t const* sibling; + for( sibling = obj->u.c.child; sibling; sibling = sibling->sibling ) + if ( sibling->name && !strcmp( sibling->name, property ) ) + return sibling; + return 0; +} + +/* Search a property by its name in a JSON object and return its value. */ +char const* json_getPropertyValue( json_t const* obj, char const* property ) { + json_t const* field = json_getProperty( obj, property ); + if ( !field ) return 0; + jsonType_t type = json_getType( field ); + if ( JSON_ARRAY >= type ) return 0; + return json_getValue( field ); +} + +/* Internal prototypes: */ +static char* goBlank( char* str ); +static char* goNum( char* str ); +static json_t* poolInit( jsonPool_t* pool ); +static json_t* poolAlloc( jsonPool_t* pool ); +static char* objValue( char* ptr, json_t* obj, jsonPool_t* pool ); +static char* setToNull( char* ch ); +static bool isEndOfPrimitive( char ch ); + +/* Parse a string to get a json. */ +json_t const* json_createWithPool( char *str, jsonPool_t *pool ) { + char* ptr = goBlank( str ); + if ( !ptr || *ptr != '{' ) return 0; + json_t* obj = pool->init( pool ); + obj->name = 0; + obj->sibling = 0; + obj->u.c.child = 0; + ptr = objValue( ptr, obj, pool ); + if ( !ptr ) return 0; + return obj; +} + +/* Parse a string to get a json. */ +json_t const* json_create( char* str, json_t mem[], unsigned int qty ) { + jsonStaticPool_t spool = { + .mem = mem, + .qty = qty, + .pool = { + .init = poolInit, + .alloc = poolAlloc + } + }; + return json_createWithPool( str, &spool.pool ); +} + +/** Get a special character with its escape character. Examples: + * 'b' -> '\b', 'n' -> '\n', 't' -> '\t' + * @param ch The escape character. + * @return The character code. */ +static char getEscape( char ch ) { + static struct { char ch; char code; } const pair[] = { + { '\"', '\"' }, { '\\', '\\' }, + { '/', '/' }, { 'b', '\b' }, + { 'f', '\f' }, { 'n', '\n' }, + { 'r', '\r' }, { 't', '\t' }, + }; + unsigned int i; + for( i = 0; i < sizeof pair / sizeof *pair; ++i ) + if ( pair[i].ch == ch ) + return pair[i].code; + return '\0'; +} + +/** Parse 4 characters. + * @Param str Pointer to first digit. + * @retval '?' If the four characters are hexadecimal digits. + * @retcal '\0' In other cases. */ +static unsigned char getCharFromUnicode( unsigned char const* str ) { + unsigned int i; + for( i = 0; i < 4; ++i ) + if ( !isxdigit( str[i] ) ) + return '\0'; + return '?'; +} + +/** Parse a string and replace the scape characters by their meaning characters. + * This parser stops when finds the character '\"'. Then replaces '\"' by '\0'. + * @param str Pointer to first character. + * @retval Pointer to first non white space after the string. If success. + * @retval Null pointer if any error occur. */ +static char* parseString( char* str ) { + unsigned char* head = (unsigned char*)str; + unsigned char* tail = (unsigned char*)str; + for( ; *head >= ' '; ++head, ++tail ) { + if ( *head == '\"' ) { + *tail = '\0'; + return (char*)++head; + } + if ( *head == '\\' ) { + if ( *++head == 'u' ) { + char const ch = getCharFromUnicode( ++head ); + if ( ch == '\0' ) return 0; + *tail = ch; + head += 3; + } + else { + char const esc = getEscape( *head ); + if ( esc == '\0' ) return 0; + *tail = esc; + } + } + else *tail = *head; + } + return 0; +} + +/** Parse a string to get the name of a property. + * @param str Pointer to first character. + * @param property The property to assign the name. + * @retval Pointer to first of property value. If success. + * @retval Null pointer if any error occur. */ +static char* propertyName( char* ptr, json_t* property ) { + property->name = ++ptr; + ptr = parseString( ptr ); + if ( !ptr ) return 0; + ptr = goBlank( ptr ); + if ( !ptr ) return 0; + if ( *ptr++ != ':' ) return 0; + return goBlank( ptr ); +} + +/** Parse a string to get the value of a property when its type is JSON_TEXT. + * @param str Pointer to first character ('\"'). + * @param property The property to assign the name. + * @retval Pointer to first non white space after the string. If success. + * @retval Null pointer if any error occur. */ +static char* textValue( char* ptr, json_t* property ) { + ++property->u.value; + ptr = parseString( ++ptr ); + if ( !ptr ) return 0; + property->type = JSON_TEXT; + return ptr; +} + +/** Compare two strings until get the null character in the second one. + * @param ptr sub string + * @param str main string + * @retval Pointer to next character. + * @retval Null pointer if any error occur. */ +static char* checkStr( char* ptr, char const* str ) { + while( *str ) + if ( *ptr++ != *str++ ) + return 0; + return ptr; +} + +/** Parser a string to get a primitive value. + * If the first character after the value is different of '}' or ']' is set to '\0'. + * @param str Pointer to first character. + * @param property Property handler to set the value and the type, (true, false or null). + * @param value String with the primitive literal. + * @param type The code of the type. ( JSON_BOOLEAN or JSON_NULL ) + * @retval Pointer to first non white space after the string. If success. + * @retval Null pointer if any error occur. */ +static char* primitiveValue( char* ptr, json_t* property, char const* value, jsonType_t type ) { + ptr = checkStr( ptr, value ); + if ( !ptr || !isEndOfPrimitive( *ptr ) ) return 0; + ptr = setToNull( ptr ); + property->type = type; + return ptr; +} + +/** Parser a string to get a true value. + * If the first character after the value is different of '}' or ']' is set to '\0'. + * @param str Pointer to first character. + * @param property Property handler to set the value and the type, (true, false or null). + * @retval Pointer to first non white space after the string. If success. + * @retval Null pointer if any error occur. */ +static char* trueValue( char* ptr, json_t* property ) { + return primitiveValue( ptr, property, "true", JSON_BOOLEAN ); +} + +/** Parser a string to get a false value. + * If the first character after the value is different of '}' or ']' is set to '\0'. + * @param str Pointer to first character. + * @param property Property handler to set the value and the type, (true, false or null). + * @retval Pointer to first non white space after the string. If success. + * @retval Null pointer if any error occur. */ +static char* falseValue( char* ptr, json_t* property ) { + return primitiveValue( ptr, property, "false", JSON_BOOLEAN ); +} + +/** Parser a string to get a null value. + * If the first character after the value is different of '}' or ']' is set to '\0'. + * @param str Pointer to first character. + * @param property Property handler to set the value and the type, (true, false or null). + * @retval Pointer to first non white space after the string. If success. + * @retval Null pointer if any error occur. */ +static char* nullValue( char* ptr, json_t* property ) { + return primitiveValue( ptr, property, "null", JSON_NULL ); +} + +/** Analyze the exponential part of a real number. + * @param str Pointer to first character. + * @retval Pointer to first non numerical after the string. If success. + * @retval Null pointer if any error occur. */ +static char* expValue( char* ptr ) { + if ( *ptr == '-' || *ptr == '+' ) ++ptr; + if ( !isdigit( *ptr ) ) return 0; + ptr = goNum( ++ptr ); + return ptr; +} + +/** Analyze the decimal part of a real number. + * @param str Pointer to first character. + * @retval Pointer to first non numerical after the string. If success. + * @retval Null pointer if any error occur. */ +static char* fraqValue( char* ptr ) { + if ( !isdigit( *ptr ) ) return 0; + ptr = goNum( ++ptr ); + if ( !ptr ) return 0; + return ptr; +} + +/** Parser a string to get a numerical value. + * If the first character after the value is different of '}' or ']' is set to '\0'. + * @param str Pointer to first character. + * @param property Property handler to set the value and the type: JSON_REAL or JSON_INTEGER. + * @retval Pointer to first non white space after the string. If success. + * @retval Null pointer if any error occur. */ +static char* numValue( char* ptr, json_t* property ) { + if ( *ptr == '-' ) ++ptr; + if ( !isdigit( *ptr ) ) return 0; + if ( *ptr != '0' ) { + ptr = goNum( ptr ); + if ( !ptr ) return 0; + } + else if ( isdigit( *++ptr ) ) return 0; + property->type = JSON_INTEGER; + if ( *ptr == '.' ) { + ptr = fraqValue( ++ptr ); + if ( !ptr ) return 0; + property->type = JSON_REAL; + } + if ( *ptr == 'e' || *ptr == 'E' ) { + ptr = expValue( ++ptr ); + if ( !ptr ) return 0; + property->type = JSON_REAL; + } + if ( !isEndOfPrimitive( *ptr ) ) return 0; + if ( JSON_INTEGER == property->type ) { + char const* value = property->u.value; + bool const negative = *value == '-'; + static char const min[] = "-9223372036854775808"; + static char const max[] = "9223372036854775807"; + unsigned int const maxdigits = ( negative? sizeof min: sizeof max ) - 1; + unsigned int const len = ptr - value; + if ( len > maxdigits ) return 0; + if ( len == maxdigits ) { + char const tmp = *ptr; + *ptr = '\0'; + char const* const threshold = negative ? min: max; + if ( 0 > strcmp( threshold, value ) ) return 0; + *ptr = tmp; + } + } + ptr = setToNull( ptr ); + return ptr; +} + +/** Add a property to a JSON object or array. + * @param obj The handler of the JSON object or array. + * @param property The handler of the property to be added. */ +static void add( json_t* obj, json_t* property ) { + property->sibling = 0; + if ( !obj->u.c.child ){ + obj->u.c.child = property; + obj->u.c.last_child = property; + } else { + obj->u.c.last_child->sibling = property; + obj->u.c.last_child = property; + } +} + +/** Parser a string to get a json object value. + * @param str Pointer to first character. + * @param pool The handler of a json pool for creating json instances. + * @retval Pointer to first character after the value. If success. + * @retval Null pointer if any error occur. */ +static char* objValue( char* ptr, json_t* obj, jsonPool_t* pool ) { + obj->type = JSON_OBJ; + obj->u.c.child = 0; + obj->sibling = 0; + ptr++; + for(;;) { + ptr = goBlank( ptr ); + if ( !ptr ) return 0; + if ( *ptr == ',' ) { + ++ptr; + continue; + } + char const endchar = ( obj->type == JSON_OBJ )? '}': ']'; + if ( *ptr == endchar ) { + *ptr = '\0'; + json_t* parentObj = obj->sibling; + if ( !parentObj ) return ++ptr; + obj->sibling = 0; + obj = parentObj; + ++ptr; + continue; + } + json_t* property = pool->alloc( pool ); + if ( !property ) return 0; + if( obj->type != JSON_ARRAY ) { + if ( *ptr != '\"' ) return 0; + ptr = propertyName( ptr, property ); + if ( !ptr ) return 0; + } + else property->name = 0; + add( obj, property ); + property->u.value = ptr; + switch( *ptr ) { + case '{': + property->type = JSON_OBJ; + property->u.c.child = 0; + property->sibling = obj; + obj = property; + ++ptr; + break; + case '[': + property->type = JSON_ARRAY; + property->u.c.child = 0; + property->sibling = obj; + obj = property; + ++ptr; + break; + case '\"': ptr = textValue( ptr, property ); break; + case 't': ptr = trueValue( ptr, property ); break; + case 'f': ptr = falseValue( ptr, property ); break; + case 'n': ptr = nullValue( ptr, property ); break; + default: ptr = numValue( ptr, property ); break; + } + if ( !ptr ) return 0; + } +} + +/** Initialize a json pool. + * @param pool The handler of the pool. + * @return a instance of a json. */ +static json_t* poolInit( jsonPool_t* pool ) { + jsonStaticPool_t *spool = json_containerOf( pool, jsonStaticPool_t, pool ); + spool->nextFree = 1; + return spool->mem; +} + +/** Create an instance of a json from a pool. + * @param pool The handler of the pool. + * @retval The handler of the new instance if success. + * @retval Null pointer if the pool was empty. */ +static json_t* poolAlloc( jsonPool_t* pool ) { + jsonStaticPool_t *spool = json_containerOf( pool, jsonStaticPool_t, pool ); + if ( spool->nextFree >= spool->qty ) return 0; + return spool->mem + spool->nextFree++; +} + +/** Checks whether an character belongs to set. + * @param ch Character value to be checked. + * @param set Set of characters. It is just a null-terminated string. + * @return true or false there is membership or not. */ +static bool isOneOfThem( char ch, char const* set ) { + while( *set != '\0' ) + if ( ch == *set++ ) + return true; + return false; +} + +/** Increases a pointer while it points to a character that belongs to a set. + * @param str The initial pointer value. + * @param set Set of characters. It is just a null-terminated string. + * @return The final pointer value or null pointer if the null character was found. */ +static char* goWhile( char* str, char const* set ) { + for(; *str != '\0'; ++str ) { + if ( !isOneOfThem( *str, set ) ) + return str; + } + return 0; +} + +/** Set of characters that defines a blank. */ +static char const* const blank = " \n\r\t\f"; + +/** Increases a pointer while it points to a white space character. + * @param str The initial pointer value. + * @return The final pointer value or null pointer if the null character was found. */ +static char* goBlank( char* str ) { + return goWhile( str, blank ); +} + +/** Increases a pointer while it points to a decimal digit character. + * @param str The initial pointer value. + * @return The final pointer value or null pointer if the null character was found. */ +static char* goNum( char* str ) { + for( ; *str != '\0'; ++str ) { + if ( !isdigit( *str ) ) + return str; + } + return 0; +} + +/** Set of characters that defines the end of an array or a JSON object. */ +static char const* const endofblock = "}]"; + +/** Set a char to '\0' and increase its pointer if the char is different to '}' or ']'. + * @param ch Pointer to character. + * @return Final value pointer. */ +static char* setToNull( char* ch ) { + if ( !isOneOfThem( *ch, endofblock ) ) *ch++ = '\0'; + return ch; +} + +/** Indicate if a character is the end of a primitive value. */ +static bool isEndOfPrimitive( char ch ) { + return ch == ',' || isOneOfThem( ch, blank ) || isOneOfThem( ch, endofblock ); +} diff --git a/backend-c/libs/tiny-json/tiny-json.h b/backend-c/libs/tiny-json/tiny-json.h new file mode 100644 index 0000000..d592a4a --- /dev/null +++ b/backend-c/libs/tiny-json/tiny-json.h @@ -0,0 +1,176 @@ + +/* + + + + Licensed under the MIT License . + SPDX-License-Identifier: MIT + Copyright (c) 2016-2018 Rafa Garcia . + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ + +#ifndef _TINY_JSON_H_ +#define _TINY_JSON_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#define json_containerOf( ptr, type, member ) \ + ((type*)( (char*)ptr - offsetof( type, member ) )) + +/** @defgroup tinyJson Tiny JSON parser. + * @{ */ + +/** Enumeration of codes of supported JSON properties types. */ +typedef enum { + JSON_OBJ, JSON_ARRAY, JSON_TEXT, JSON_BOOLEAN, + JSON_INTEGER, JSON_REAL, JSON_NULL +} jsonType_t; + +/** Structure to handle JSON properties. */ +typedef struct json_s { + struct json_s* sibling; + char const* name; + union { + char const* value; + struct { + struct json_s* child; + struct json_s* last_child; + } c; + } u; + jsonType_t type; +} json_t; + +/** Parse a string to get a json. + * @param str String pointer with a JSON object. It will be modified. + * @param mem Array of json properties to allocate. + * @param qty Number of elements of mem. + * @retval Null pointer if any was wrong in the parse process. + * @retval If the parser process was successfully a valid handler of a json. + * This property is always unnamed and its type is JSON_OBJ. */ +json_t const* json_create( char* str, json_t mem[], unsigned int qty ); + +/** Get the name of a json property. + * @param json A valid handler of a json property. + * @retval Pointer to null-terminated if property has name. + * @retval Null pointer if the property is unnamed. */ +static inline char const* json_getName( json_t const* json ) { + return json->name; +} + +/** Get the value of a json property. + * The type of property cannot be JSON_OBJ or JSON_ARRAY. + * @param json A valid handler of a json property. + * @return Pointer to null-terminated string with the value. */ +static inline char const* json_getValue( json_t const* property ) { + return property->u.value; +} + +/** Get the type of a json property. + * @param json A valid handler of a json property. + * @return The code of type.*/ +static inline jsonType_t json_getType( json_t const* json ) { + return json->type; +} + +/** Get the next sibling of a JSON property that is within a JSON object or array. + * @param json A valid handler of a json property. + * @retval The handler of the next sibling if found. + * @retval Null pointer if the json property is the last one. */ +static inline json_t const* json_getSibling( json_t const* json ) { + return json->sibling; +} + +/** Search a property by its name in a JSON object. + * @param obj A valid handler of a json object. Its type must be JSON_OBJ. + * @param property The name of property to get. + * @retval The handler of the json property if found. + * @retval Null pointer if not found. */ +json_t const* json_getProperty( json_t const* obj, char const* property ); + + +/** Search a property by its name in a JSON object and return its value. + * @param obj A valid handler of a json object. Its type must be JSON_OBJ. + * @param property The name of property to get. + * @retval If found a pointer to null-terminated string with the value. + * @retval Null pointer if not found or it is an array or an object. */ +char const* json_getPropertyValue( json_t const* obj, char const* property ); + +/** Get the first property of a JSON object or array. + * @param json A valid handler of a json property. + * Its type must be JSON_OBJ or JSON_ARRAY. + * @retval The handler of the first property if there is. + * @retval Null pointer if the json object has not properties. */ +static inline json_t const* json_getChild( json_t const* json ) { + return json->u.c.child; +} + +/** Get the value of a json boolean property. + * @param property A valid handler of a json object. Its type must be JSON_BOOLEAN. + * @return The value stdbool. */ +static inline bool json_getBoolean( json_t const* property ) { + return *property->u.value == 't'; +} + +/** Get the value of a json integer property. + * @param property A valid handler of a json object. Its type must be JSON_INTEGER. + * @return The value stdint. */ +static inline int64_t json_getInteger( json_t const* property ) { + return atoll( property->u.value ); +} + +/** Get the value of a json real property. + * @param property A valid handler of a json object. Its type must be JSON_REAL. + * @return The value. */ +static inline double json_getReal( json_t const* property ) { + return atof( property->u.value ); +} + + + +/** Structure to handle a heap of JSON properties. */ +typedef struct jsonPool_s jsonPool_t; +struct jsonPool_s { + json_t* (*init)( jsonPool_t* pool ); + json_t* (*alloc)( jsonPool_t* pool ); +}; + +/** Parse a string to get a json. + * @param str String pointer with a JSON object. It will be modified. + * @param pool Custom json pool pointer. + * @retval Null pointer if any was wrong in the parse process. + * @retval If the parser process was successfully a valid handler of a json. + * This property is always unnamed and its type is JSON_OBJ. */ +json_t const* json_createWithPool( char* str, jsonPool_t* pool ); + +/** @ } */ + +#ifdef __cplusplus +} +#endif + +#endif /* _TINY_JSON_H_ */ diff --git a/backend-c/sdk/allocator.c b/backend-c/sdk/allocator.c new file mode 100644 index 0000000..15e49ff --- /dev/null +++ b/backend-c/sdk/allocator.c @@ -0,0 +1,13 @@ +#include "allocator.h" +#include + +#define UNUSED(x) (void)(x) + +void *allocate(size_t size) { + return malloc(size); +} + +void deallocate(void *ptr, size_t size) { + UNUSED(size); + free(ptr); +} diff --git a/backend-c/sdk/allocator.h b/backend-c/sdk/allocator.h new file mode 100644 index 0000000..690a90b --- /dev/null +++ b/backend-c/sdk/allocator.h @@ -0,0 +1,27 @@ +#ifndef FLUENCE_C_SDK_ALLOCATOR_H +#define FLUENCE_C_SDK_ALLOCATOR_H + +#include // for size_t + +/** + * Allocates a memory region of a given size. + * + * Used by Wasm VM for byte array passing. Should be exported from module. + * + * @param size a size of allocated memory region. + * @return a pointer to the allocated memory region. + */ +void *allocate(size_t size); + +/** + * Frees a memory region. + * + * Used by Wasm VM for freeing previous memory allocated by `allocate` function. + * Should be exported from module. + * + * @param ptr a pointer to the previously allocated memory region. + * @param size a size of the previously allocated memory region. + */ +void deallocate(void *ptr, size_t size); + +#endif //FLUENCE_C_SDK_ALLOCATOR_H diff --git a/backend-c/sdk/logger.c b/backend-c/sdk/logger.c new file mode 100644 index 0000000..7a6f881 --- /dev/null +++ b/backend-c/sdk/logger.c @@ -0,0 +1,15 @@ +#include "logger.h" + +#define __LOGGER_IMPORT(name) \ + __attribute__((__import_module__("logger"), __import_name__(#name))) + +void __write(char ch) __LOGGER_IMPORT(write); +void __flush() __LOGGER_IMPORT(flush); + +void wasm_log(const char *str, int len) { + for(int byteId = 0; byteId < len; ++byteId) { + __write(str[byteId]); + } + + __flush(); +} diff --git a/backend-c/sdk/logger.h b/backend-c/sdk/logger.h new file mode 100644 index 0000000..d53d6b5 --- /dev/null +++ b/backend-c/sdk/logger.h @@ -0,0 +1,12 @@ +#ifndef FLUENCE_C_SDK_LOGGER_H +#define FLUENCE_C_SDK_LOGGER_H + +/** + * Writes provided string to Wasm VM logger. + * + * @param str a pointer to a message that should be logged. + * @param len a size of a message that should be logged. + */ +void wasm_log(const char *str, int len); + +#endif //FLUENCE_C_SDK_LOGGER_H diff --git a/backend-c/sdk/side_module_api.h b/backend-c/sdk/side_module_api.h new file mode 100644 index 0000000..1bebd92 --- /dev/null +++ b/backend-c/sdk/side_module_api.h @@ -0,0 +1,61 @@ +#ifndef C_TEMPLATE_SIDE_MODULE_API_H +#define C_TEMPLATE_SIDE_MODULE_API_H + +//#include + +/* + * Concatenate preprocessor tokens A and B without expanding macro definitions + * (however, if invoked from a macro, macro arguments are expanded). + */ +#define PPCAT_NX(A, B) A ## B + +/* + * Concatenate preprocessor tokens A and B after macro-expanding them. + */ +#define PPCAT(A, B) PPCAT_NX(A, B) + +/* + * Turn A into a string literal without expanding macro definitions + * (however, if invoked from a macro, macro arguments are expanded). + */ +#define STRINGIZE_NX(A) #A + +/* + * Turn A into a string literal after macro-expanding it. + */ +#define STRINGIZE(A) STRINGIZE_NX(A) + + +#define __MODULE_IMPORT(module_name) \ + void module_name ## _store(char *ptr, char byte) __attribute__((__import_module__(#module_name), __import_name__(STRINGIZE(PPCAT(module_name, _store))))); \ + unsigned char module_name ## _load(char *ptr) __attribute__((__import_module__(#module_name), __import_name__(STRINGIZE(PPCAT(module_name, _load))))); \ + char* module_name ## _allocate(unsigned int size) __attribute__((__import_module__(#module_name), __import_name__(STRINGIZE(PPCAT(module_name, _allocate))))); \ + void module_name ## _deallocate(char *ptr, unsigned int size) __attribute__((__import_module__(#module_name), __import_name__(STRINGIZE(PPCAT(module_name, _deallocate))))); \ + char* module_name ## _invoke(char *ptr, int size) __attribute__((__import_module__(#module_name), __import_name__(STRINGIZE(PPCAT(module_name, _invoke))))); \ + \ +char * module_name ## _call(const char *ptr, int length) { \ + char *request_ptr = module_name ## _allocate(length); \ + \ + for(int i = 0; i < length; ++i) { \ + module_name ## _store(request_ptr + i, ptr[i]); \ + } \ + char *result = module_name ## _invoke(request_ptr, length); \ + \ + unsigned int result_size = 0; \ + for (int i = 0; i < 4; ++i) { \ + result_size = result_size | ((unsigned int)result[i] << 8*i); \ + } \ + \ + char *result_out = malloc(result_size + 1); \ + for(int i = 0; i < result_size; ++i) { \ + result_out[i] = module_name ## _load(result + 4 + i); \ + }\ + \ + result_out[result_size] = '\0'; \ + \ + module_name ## _deallocate(result, result_size); \ + \ + return result_out;\ +} + +#endif //C_TEMPLATE_SIDE_MODULE_API_H diff --git a/backend-c/src/main.c b/backend-c/src/main.c new file mode 100644 index 0000000..914eb4d --- /dev/null +++ b/backend-c/src/main.c @@ -0,0 +1,143 @@ +#include "../sdk/allocator.h" +#include "../sdk/logger.h" +#include "model.h" +#include "../libs/tiny-json/tiny-json.h" + +//#include + +char *prepare_response(const char *response, int response_length) { + const int RESPONSE_SIZE_BYTES = 4; + char *result = (char *)allocate(response_length + RESPONSE_SIZE_BYTES); + + for(int i = 0; i < RESPONSE_SIZE_BYTES; ++i) { + result[i] = (response_length >> 8*i) & 0xFF; + } + + memcpy(result + RESPONSE_SIZE_BYTES, response, response_length); + + return result; +} + +const char *add_post_request(const json_t *json); +const char *fetch_posts_request(const json_t *json); + +char *invoke(char *str, int length) { + wasm_log(str, length); + + json_t pool[10]; + const unsigned int pool_size = sizeof pool / sizeof *pool; + + const json_t *json = json_create(str, pool, pool_size); + if(!json) { + const char error[] = "Mailformed json given"; + return prepare_response(error, sizeof error); + } + + json_t const *action_json = json_getProperty(json, "action"); + if(0 == action_json) { + const char error[] = "Given json doesn't contain action field"; + return prepare_response(error, sizeof error); + } + + if(JSON_TEXT != json_getType(action_json)) { + const char error[] = "action field is mailformed"; + return prepare_response(error, sizeof error); + } + + const char *action = json_getValue(action_json); + + const char *result = ""; + if(0 == strcmp(action, "Post")) { + result = add_post_request(json); + } else if(0 == strcmp(action, "Fetch")) { + result = fetch_posts_request(json); + } else { + char *error = (char *)malloc(1024); + const int error_size = snprintf(error, 1024, "%s given as the action field, but only `Post` and `Fetch` are supported"); + result = prepare_response(error, error_size); + } + + return result; +} + +const char *add_post_request(const json_t *json) { + const json_t *message_json = json_getProperty(json, "message"); + const json_t *username_json = json_getProperty(json, "username"); + if(0 == message_json || 0 == username_json) { + const char error[] = "Given json doesn't contain message or username field"; + return prepare_response(error, sizeof error); + } + + if(JSON_TEXT != json_getType(message_json) || JSON_TEXT != json_getType(username_json)) { + const char error[] = "message or action fields are mailformed"; + return prepare_response(error, sizeof error); + } + + const char *message = json_getValue(message_json); + const char *username = json_getValue(username_json); + + const char *add_post_result = add_post(message, strlen(message), + username, strlen(username)); + if(0 == add_post_result) { + const char error[] = "add_post failed"; + return prepare_response(error, sizeof error); + } + + const char *count = get_posts_count(); + if(0 == count) { + const char error[] = "get_posts_count failed"; + return prepare_response(error, sizeof error); + } + + char result[256]; + const int result_len = snprintf(result, 256, "{ count: \"%s\" }", count); + + return prepare_response(result, result_len); +} + +const char *fetch_posts_request(const json_t *json) { + const json_t *username_json = json_getProperty(json, "username"); + const json_t *offset_json = json_getProperty(json, "offset"); + const json_t *count_json = json_getProperty(json, "count"); + + if((0 != username_json && JSON_TEXT != json_getType(username_json)) || + (0 != offset_json && JSON_INTEGER != json_getType(offset_json)) || + (0 != count_json && JSON_INTEGER != json_getType(count_json)) + ) { + const char error[] = "json fields are mailformed"; + return prepare_response(error, sizeof(error)); + } + + int count = 100; + if(0 != count_json) { + count = json_getInteger(count_json); + } + + int offset = 0; + if(0 != offset_json) { + offset = json_getInteger(offset_json); + } + + char *result = ""; + if(0 == username_json) { + result = get_all_posts(offset, count); + if(0 == result) { + const char error[] = "get_all_posts failed"; + return prepare_response(error, sizeof(error)); + } + } else { + const char *username = json_getValue(username_json); + result = get_posts_by_username(username, strlen(username), offset, count); + if(0 == result) { + const char error[] = "get_posts_by_username failed"; + return prepare_response(error, sizeof(error)); + } + } + + const int result_len = strlen(result) + 100; + char *result_out = malloc(result_len); + + const int result_out_len = snprintf(result_out, result_len, "{ posts: \"%s\" }", result); + + return prepare_response(result_out, result_out_len); +} diff --git a/backend-c/src/model.c b/backend-c/src/model.c new file mode 100644 index 0000000..1795635 --- /dev/null +++ b/backend-c/src/model.c @@ -0,0 +1,67 @@ +#include "../sdk/side_module_api.h" +//#include +//#include + +__MODULE_IMPORT(sqlite) +int snprintf ( char * s, int n, const char * format, ... ); + +// TODO: introduce error codes and security checks + +void create_scheme() { + const char create_sql[] = "CREATE TABLE messages(message text, username text)"; + sqlite_call(create_sql, sizeof(create_sql)); +} + +char *add_post(const char *message, int message_length, const char *username, int username_length) { + // at now wasm-ld has 1024 bytes for stack permission by default - that why dynamic allocation here + const int request_size = message_length + username_length + 50; + char *add_sql = (char *)malloc(request_size); + + const int add_sql_length = snprintf(add_sql, request_size, "INSERT INTO messages VALUES(%s, %s)", message, username); + if(add_sql_length < 0) { + return 0; + } + + return sqlite_call(add_sql, add_sql_length); +} + +char *get_all_posts(int offset, int count) { + // at now wasm-ld has 1024 bytes for stack permission by default - that why dynamic allocation here + char *get_sql = (char *)malloc(256); + + const int get_sql_length = snprintf(get_sql, 256, + "SELECT json_group_array(\n" + " json_object('message', message, 'username', username)\n" + " ) AS json_result FROM (\n" + " SELECT * FROM messages LIMIT %d OFFSET %d\n" + " )", count, offset); + if(get_sql_length < 0) { + return 0; + } + + return sqlite_call(get_sql, get_sql_length); +} + +char *get_posts_by_username(const char *username, int username_length, int offset, int count) { + // at now wasm-ld has 1024 bytes for stack permission by default - that why dynamic allocation here + const int request_size = username_length + 300; + char *get_sql = (char *)malloc(request_size); + + const int add_sql_length = snprintf(get_sql, request_size, + "\"SELECT json_group_array(\n" + " json_object('message', message, 'username', username)\n" + " ) AS json_result FROM (\n" + " SELECT * FROM messages where username = '%s' LIMIT %d OFFSET %d\n" + " )", + username, count, offset); + if(add_sql_length < 0) { + return 0; + } + + return sqlite_call(get_sql, add_sql_length); +} + +char *get_posts_count() { + const char count_sql[] = "SELECT COUNT(*) from messages"; + return sqlite_call(count_sql, sizeof(count_sql)); +} diff --git a/backend-c/src/model.h b/backend-c/src/model.h new file mode 100644 index 0000000..eb634cb --- /dev/null +++ b/backend-c/src/model.h @@ -0,0 +1,14 @@ +#ifndef FLUID_MODEL_H +#define FLUID_MODEL_H + +void create_scheme(); + +char *add_post(const char *message, int message_length, const char *username, int username_length); + +char *get_all_posts(int offset, int count); + +char *get_posts_by_username(const char *username, int username_length, int offset, int count); + +char *get_posts_count(); + +#endif //FLUID_MODEL_H