diff --git a/.mark b/.mark new file mode 100644 index 0000000..146ba45 --- /dev/null +++ b/.mark @@ -0,0 +1 @@ +Thu Jan 9 01:31:07 CST 2025 diff --git a/ABC b/ABC new file mode 100644 index 0000000..4e3dffe --- /dev/null +++ b/ABC @@ -0,0 +1 @@ +HELLO WORLD diff --git a/LICENSE.txt.rc.cpp b/LICENSE.txt.rc.cpp new file mode 100644 index 0000000..af978c6 Binary files /dev/null and b/LICENSE.txt.rc.cpp differ diff --git a/LICENSE.txt.rc.cpp.dec b/LICENSE.txt.rc.cpp.dec new file mode 100644 index 0000000..057af61 --- /dev/null +++ b/LICENSE.txt.rc.cpp.dec @@ -0,0 +1,203 @@ + + 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. + + VV "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 + y(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 + gg 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, + 2publicly 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 + y] 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 + h 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 lim�ted 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 + 4� 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/LICENSE.txt.rc.js b/LICENSE.txt.rc.js new file mode 100644 index 0000000..399f152 Binary files /dev/null and b/LICENSE.txt.rc.js differ diff --git a/LICENSE.txt.rc.js.dec b/LICENSE.txt.rc.js.dec new file mode 100644 index 0000000..057af61 --- /dev/null +++ b/LICENSE.txt.rc.js.dec @@ -0,0 +1,203 @@ + + 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. + + VV "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 + y(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 + gg 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, + 2publicly 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 + y] 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 + h 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 lim�ted 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 + 4� 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/Makefile b/Makefile index c745934..563469a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,8 @@ CXX = /opt/homebrew/opt/llvm/bin/clang++ EMCC = emcc # Flags -CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic -O3 -march=native +#CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic -O3 -march=native +CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic -O0 -fsanitize=address -march=native CXXFLAGS += -isysroot $(shell xcrun --show-sdk-path) CXXFLAGS += -fopenmp -I/opt/homebrew/opt/llvm/include DEPFLAGS = -MMD -MF $(@:.o=.d) @@ -12,7 +13,7 @@ LDFLAGS = -fopenmp -L/opt/homebrew/opt/llvm/lib -lz -lc++ # Emscripten Flags for WASM # Include your new bridging funcs in EXPORTED_FUNCTIONS: -EMCCFLAGS = -O3 -s WASM=1 \ +EMCCFLAGS = -O0 -s WASM=1 \ -s EXPORTED_FUNCTIONS="['_rainbowHash64','_rainbowHash128','_rainbowHash256','_rainstormHash64', '_rainstormHash128', '_rainstormHash256', '_rainstormHash512', 'stringToUTF8','UTF8ToString', 'lengthBytesUTF8','_malloc','_free','_wasmGetFileHeaderInfo','_wasmFree', '_wasmStreamEncryptBuffer', '_wasmStreamDecryptBuffer', '_wasmFreeBuffer']" \ -s EXPORTED_RUNTIME_METHODS="['wasmExports','ccall','cwrap']" \ -s WASM_BIGINT=1 \ diff --git a/Makefile.rc.cpp b/Makefile.rc.cpp new file mode 100644 index 0000000..4be42c1 Binary files /dev/null and b/Makefile.rc.cpp differ diff --git a/Makefile.rc.cpp.dec b/Makefile.rc.cpp.dec new file mode 100644 index 0000000..563469a --- /dev/null +++ b/Makefile.rc.cpp.dec @@ -0,0 +1,98 @@ +# Compiler and Tools +CXX = /opt/homebrew/opt/llvm/bin/clang++ +EMCC = emcc + +# Flags +#CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic -O3 -march=native +CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic -O0 -fsanitize=address -march=native +CXXFLAGS += -isysroot $(shell xcrun --show-sdk-path) +CXXFLAGS += -fopenmp -I/opt/homebrew/opt/llvm/include +DEPFLAGS = -MMD -MF $(@:.o=.d) + +LDFLAGS = -fopenmp -L/opt/homebrew/opt/llvm/lib -lz -lc++ + +# Emscripten Flags for WASM +# Include your new bridging funcs in EXPORTED_FUNCTIONS: +EMCCFLAGS = -O0 -s WASM=1 \ + -s EXPORTED_FUNCTIONS="['_rainbowHash64','_rainbowHash128','_rainbowHash256','_rainstormHash64', '_rainstormHash128', '_rainstormHash256', '_rainstormHash512', 'stringToUTF8','UTF8ToString', 'lengthBytesUTF8','_malloc','_free','_wasmGetFileHeaderInfo','_wasmFree', '_wasmStreamEncryptBuffer', '_wasmStreamDecryptBuffer', '_wasmFreeBuffer']" \ + -s EXPORTED_RUNTIME_METHODS="['wasmExports','ccall','cwrap']" \ + -s WASM_BIGINT=1 \ + -s ALLOW_MEMORY_GROWTH=1 \ + -s USE_ZLIB=1 \ + -g + +# Directories +OBJDIR = rain/obj +BUILDDIR = rain/bin +WASMDIR = js/wasm + +# Sources and Outputs +SRCS = $(wildcard src/*.cpp) +OBJS = $(addprefix $(OBJDIR)/,$(notdir $(SRCS:.cpp=.o))) +DEPS = $(OBJS:.o=.d) + +STORM_WASM_SOURCE = src/rainstorm.cpp +BOW_WASM_SOURCE = src/rainbow.cpp +HEADER_WASM_SOURCE = src/wasm/exports.cpp # or wherever your wasm bridging is +WASM_OUTPUT = docs/rain.wasm +JS_OUTPUT = docs/rain.cjs + +# Default Target +all: directories node_modules rainsum link rainwasm + +# Create Necessary Directories +directories: ${OBJDIR} ${BUILDDIR} ${WASMDIR} + +${OBJDIR}: + mkdir -p ${OBJDIR} + +${BUILDDIR}: + mkdir -p ${BUILDDIR} + +${WASMDIR}: + mkdir -p ${WASMDIR} + +# Install Node Modules +node_modules: + @(test ! -d ./js/node_modules && cd js && npm i && cd ..) || : + @(test ! -d ./scripts/node_modules && cd scripts && npm i && cd ..) || : + +# Build Executable (C++ native) +rainsum: $(OBJS) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(BUILDDIR)/$@ $^ + +# Compile Object Files +$(OBJDIR)/%.o: src/%.cpp + $(CXX) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@ + +# Build WebAssembly Output +rainwasm: $(WASM_OUTPUT) $(JS_OUTPUT) + +# NOTE: Add your bridging source(s) to the compile command: +$(WASM_OUTPUT) $(JS_OUTPUT): $(STORM_WASM_SOURCE) $(BOW_WASM_SOURCE) $(HEADER_WASM_SOURCE) + @[ -d docs ] || mkdir -p docs + @[ -d ${WASMDIR} ] || mkdir -p ${WASMDIR} + $(EMCC) $(EMCCFLAGS) -o docs/rain.html $^ + mv docs/rain.js $(JS_OUTPUT) + cp $(WASM_OUTPUT) $(JS_OUTPUT) ${WASMDIR} + rm docs/rain.html + +# Symlink for Convenience +link: + @ln -sf rain/bin/rainsum + +# Installation +.PHONY: install +install: rainsum + cp $(BUILDDIR)/rainsum /usr/local/bin/ + +# Include Dependencies +-include $(DEPS) + +# Clean Build Artifacts +.PHONY: clean +clean: + rm -rf $(OBJDIR) $(BUILDDIR) rainsum \ + $(WASMDIR) js/node_modules scripts/node_modules \ + $(WASM_OUTPUT) $(JS_OUTPUT) + diff --git a/Makefile.rc.js b/Makefile.rc.js new file mode 100644 index 0000000..013282a Binary files /dev/null and b/Makefile.rc.js differ diff --git a/Makefile.rc.js.dec b/Makefile.rc.js.dec new file mode 100644 index 0000000..563469a --- /dev/null +++ b/Makefile.rc.js.dec @@ -0,0 +1,98 @@ +# Compiler and Tools +CXX = /opt/homebrew/opt/llvm/bin/clang++ +EMCC = emcc + +# Flags +#CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic -O3 -march=native +CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic -O0 -fsanitize=address -march=native +CXXFLAGS += -isysroot $(shell xcrun --show-sdk-path) +CXXFLAGS += -fopenmp -I/opt/homebrew/opt/llvm/include +DEPFLAGS = -MMD -MF $(@:.o=.d) + +LDFLAGS = -fopenmp -L/opt/homebrew/opt/llvm/lib -lz -lc++ + +# Emscripten Flags for WASM +# Include your new bridging funcs in EXPORTED_FUNCTIONS: +EMCCFLAGS = -O0 -s WASM=1 \ + -s EXPORTED_FUNCTIONS="['_rainbowHash64','_rainbowHash128','_rainbowHash256','_rainstormHash64', '_rainstormHash128', '_rainstormHash256', '_rainstormHash512', 'stringToUTF8','UTF8ToString', 'lengthBytesUTF8','_malloc','_free','_wasmGetFileHeaderInfo','_wasmFree', '_wasmStreamEncryptBuffer', '_wasmStreamDecryptBuffer', '_wasmFreeBuffer']" \ + -s EXPORTED_RUNTIME_METHODS="['wasmExports','ccall','cwrap']" \ + -s WASM_BIGINT=1 \ + -s ALLOW_MEMORY_GROWTH=1 \ + -s USE_ZLIB=1 \ + -g + +# Directories +OBJDIR = rain/obj +BUILDDIR = rain/bin +WASMDIR = js/wasm + +# Sources and Outputs +SRCS = $(wildcard src/*.cpp) +OBJS = $(addprefix $(OBJDIR)/,$(notdir $(SRCS:.cpp=.o))) +DEPS = $(OBJS:.o=.d) + +STORM_WASM_SOURCE = src/rainstorm.cpp +BOW_WASM_SOURCE = src/rainbow.cpp +HEADER_WASM_SOURCE = src/wasm/exports.cpp # or wherever your wasm bridging is +WASM_OUTPUT = docs/rain.wasm +JS_OUTPUT = docs/rain.cjs + +# Default Target +all: directories node_modules rainsum link rainwasm + +# Create Necessary Directories +directories: ${OBJDIR} ${BUILDDIR} ${WASMDIR} + +${OBJDIR}: + mkdir -p ${OBJDIR} + +${BUILDDIR}: + mkdir -p ${BUILDDIR} + +${WASMDIR}: + mkdir -p ${WASMDIR} + +# Install Node Modules +node_modules: + @(test ! -d ./js/node_modules && cd js && npm i && cd ..) || : + @(test ! -d ./scripts/node_modules && cd scripts && npm i && cd ..) || : + +# Build Executable (C++ native) +rainsum: $(OBJS) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(BUILDDIR)/$@ $^ + +# Compile Object Files +$(OBJDIR)/%.o: src/%.cpp + $(CXX) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@ + +# Build WebAssembly Output +rainwasm: $(WASM_OUTPUT) $(JS_OUTPUT) + +# NOTE: Add your bridging source(s) to the compile command: +$(WASM_OUTPUT) $(JS_OUTPUT): $(STORM_WASM_SOURCE) $(BOW_WASM_SOURCE) $(HEADER_WASM_SOURCE) + @[ -d docs ] || mkdir -p docs + @[ -d ${WASMDIR} ] || mkdir -p ${WASMDIR} + $(EMCC) $(EMCCFLAGS) -o docs/rain.html $^ + mv docs/rain.js $(JS_OUTPUT) + cp $(WASM_OUTPUT) $(JS_OUTPUT) ${WASMDIR} + rm docs/rain.html + +# Symlink for Convenience +link: + @ln -sf rain/bin/rainsum + +# Installation +.PHONY: install +install: rainsum + cp $(BUILDDIR)/rainsum /usr/local/bin/ + +# Include Dependencies +-include $(DEPS) + +# Clean Build Artifacts +.PHONY: clean +clean: + rm -rf $(OBJDIR) $(BUILDDIR) rainsum \ + $(WASMDIR) js/node_modules scripts/node_modules \ + $(WASM_OUTPUT) $(JS_OUTPUT) + diff --git a/TODO b/TODO index 3191d1c..b9338f3 100644 --- a/TODO +++ b/TODO @@ -4,20 +4,10 @@ cipher improvement - use 'more secure' randomness source - let key material come from a file not just a password or -P command line arg - write tests, including hmac testing tampering tests -- use nis1 and rp variants if specified. and always use nis1 as default for any cipher mode -- test research construct - weak hash -- digest mining construction --> block cipher --- davies meyer construction ---> strong hash - by prototyping in js -- implement a js version of enc / dec Later maybe - - ensure key privacy in memory, by checking raw keys: - Are wiped from memory after use (consider std::fill or explicit_bzero for zeroing memory). - Avoid storing plaintext keys in memory longer than necessary. -- stream input in for compress / decompress and stream and block ciphers. Don't just read it all in, it's crass - -- ensure we print out the inc nonce for mining and rand nonce for mining (right now we just show the iter count, and lose the rand nonce) - diff --git a/docs/rain.cjs b/docs/rain.cjs index acb9402..1383fce 100644 --- a/docs/rain.cjs +++ b/docs/rain.cjs @@ -63,6 +63,15 @@ function locateFile(path) { var readAsync, readBinary; if (ENVIRONMENT_IS_NODE) { + if (typeof process == 'undefined' || !process.release || process.release.name !== 'node') throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + + var nodeVersion = process.versions.node; + var numericVersion = nodeVersion.split('.').slice(0, 3); + numericVersion = (numericVersion[0] * 10000) + (numericVersion[1] * 100) + (numericVersion[2].split('-')[0] * 1); + var minVersion = 160000; + if (numericVersion < 160000) { + throw new Error('This emscripten-generated code requires node v16.0.0 (detected v' + nodeVersion + ')'); + } // These modules will usually be used on Node.js. Load them eagerly to avoid // the complexity of lazy-loading. @@ -76,6 +85,7 @@ readBinary = (filename) => { // We need to re-wrap `file://` strings to URLs. filename = isFileURI(filename) ? new URL(filename) : filename; var ret = fs.readFileSync(filename); + assert(Buffer.isBuffer(ret)); return ret; }; @@ -83,6 +93,7 @@ readAsync = async (filename, binary = true) => { // See the comment in the `readBinary` function. filename = isFileURI(filename) ? new URL(filename) : filename; var ret = fs.readFileSync(filename, binary ? undefined : 'utf8'); + assert(binary ? Buffer.isBuffer(ret) : typeof ret == 'string'); return ret; }; // end include: node_shell_read.js @@ -101,6 +112,11 @@ readAsync = async (filename, binary = true) => { throw toThrow; }; +} else +if (ENVIRONMENT_IS_SHELL) { + + if ((typeof process == 'object' && typeof require === 'function') || typeof window == 'object' || typeof WorkerGlobalScope != 'undefined') throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + } else // Note that this includes Node.js workers when relevant (pthreads is enabled). @@ -124,6 +140,8 @@ if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, '').lastIndexOf('/')+1); } + if (!(typeof window == 'object' || typeof WorkerGlobalScope != 'undefined')) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + { // include: web_or_worker_shell_read.js if (ENVIRONMENT_IS_WORKER) { @@ -167,6 +185,7 @@ if (ENVIRONMENT_IS_WORKER) { } } else { + throw new Error('environment detection error'); } var out = Module['print'] || console.log.bind(console); @@ -177,17 +196,44 @@ Object.assign(Module, moduleOverrides); // Free the object hierarchy contained in the overrides, this lets the GC // reclaim data used. moduleOverrides = null; +checkIncomingModuleAPI(); // Emit code to handle expected values on the Module object. This applies Module.x // to the proper local x. This has two benefits: first, we only emit it if it is // expected to arrive, and second, by using a local everywhere else that can be // minified. -if (Module['arguments']) arguments_ = Module['arguments']; +if (Module['arguments']) arguments_ = Module['arguments'];legacyModuleProp('arguments', 'arguments_'); -if (Module['thisProgram']) thisProgram = Module['thisProgram']; +if (Module['thisProgram']) thisProgram = Module['thisProgram'];legacyModuleProp('thisProgram', 'thisProgram'); // perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message +// Assertions on removed incoming Module JS APIs. +assert(typeof Module['memoryInitializerPrefixURL'] == 'undefined', 'Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['pthreadMainPrefixURL'] == 'undefined', 'Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['cdInitializerPrefixURL'] == 'undefined', 'Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['filePackagePrefixURL'] == 'undefined', 'Module.filePackagePrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['read'] == 'undefined', 'Module.read option was removed'); +assert(typeof Module['readAsync'] == 'undefined', 'Module.readAsync option was removed (modify readAsync in JS)'); +assert(typeof Module['readBinary'] == 'undefined', 'Module.readBinary option was removed (modify readBinary in JS)'); +assert(typeof Module['setWindowTitle'] == 'undefined', 'Module.setWindowTitle option was removed (modify emscripten_set_window_title in JS)'); +assert(typeof Module['TOTAL_MEMORY'] == 'undefined', 'Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY'); +legacyModuleProp('asm', 'wasmExports'); +legacyModuleProp('readAsync', 'readAsync'); +legacyModuleProp('readBinary', 'readBinary'); +legacyModuleProp('setWindowTitle', 'setWindowTitle'); +var IDBFS = 'IDBFS is no longer included by default; build with -lidbfs.js'; +var PROXYFS = 'PROXYFS is no longer included by default; build with -lproxyfs.js'; +var WORKERFS = 'WORKERFS is no longer included by default; build with -lworkerfs.js'; +var FETCHFS = 'FETCHFS is no longer included by default; build with -lfetchfs.js'; +var ICASEFS = 'ICASEFS is no longer included by default; build with -licasefs.js'; +var JSFILEFS = 'JSFILEFS is no longer included by default; build with -ljsfilefs.js'; +var OPFS = 'OPFS is no longer included by default; build with -lopfs.js'; + +var NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js'; + +assert(!ENVIRONMENT_IS_SHELL, 'shell environment detected but not enabled at build time. Add `shell` to `-sENVIRONMENT` to enable.'); + // end include: shell.js // include: preamble.js @@ -201,7 +247,11 @@ if (Module['thisProgram']) thisProgram = Module['thisProgram']; // An online HTML version (which may be of a different version of Emscripten) // is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html -var wasmBinary = Module['wasmBinary']; +var wasmBinary = Module['wasmBinary'];legacyModuleProp('wasmBinary', 'wasmBinary'); + +if (typeof WebAssembly != 'object') { + err('no native wasm support detected'); +} // Wasm globals @@ -227,13 +277,13 @@ var EXITSTATUS; /** @type {function(*, string=)} */ function assert(condition, text) { if (!condition) { - // This build was created without ASSERTIONS defined. `assert()` should not - // ever be called in this configuration but in case there are callers in - // the wild leave this simple abort() implementation here for now. - abort(text); + abort('Assertion failed' + (text ? ': ' + text : '')); } } +// We used to include malloc/free by default in the past. Show a helpful error in +// builds with assertions. + // Memory management var HEAP, @@ -276,7 +326,52 @@ function updateMemoryViews() { } // end include: runtime_shared.js +assert(!Module['STACK_SIZE'], 'STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time') + +assert(typeof Int32Array != 'undefined' && typeof Float64Array !== 'undefined' && Int32Array.prototype.subarray != undefined && Int32Array.prototype.set != undefined, + 'JS engine does not provide full typed array support'); + +// If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY +assert(!Module['wasmMemory'], 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); +assert(!Module['INITIAL_MEMORY'], 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); + // include: runtime_stack_check.js +// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. +function writeStackCookie() { + var max = _emscripten_stack_get_end(); + assert((max & 3) == 0); + // If the stack ends at address zero we write our cookies 4 bytes into the + // stack. This prevents interference with SAFE_HEAP and ASAN which also + // monitor writes to address zero. + if (max == 0) { + max += 4; + } + // The stack grow downwards towards _emscripten_stack_get_end. + // We write cookies to the final two words in the stack and detect if they are + // ever overwritten. + HEAPU32[((max)>>2)] = 0x02135467; + HEAPU32[(((max)+(4))>>2)] = 0x89BACDFE; + // Also test the global address 0 for integrity. + HEAPU32[((0)>>2)] = 1668509029; +} + +function checkStackCookie() { + if (ABORT) return; + var max = _emscripten_stack_get_end(); + // See writeStackCookie(). + if (max == 0) { + max += 4; + } + var cookie1 = HEAPU32[((max)>>2)]; + var cookie2 = HEAPU32[(((max)+(4))>>2)]; + if (cookie1 != 0x02135467 || cookie2 != 0x89BACDFE) { + abort(`Stack overflow! Stack cookie has been overwritten at ${ptrToString(max)}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${ptrToString(cookie2)} ${ptrToString(cookie1)}`); + } + // Also test the global address 0 for integrity. + if (HEAPU32[((0)>>2)] != 0x63736d65 /* 'emsc' */) { + abort('Runtime error: The application has corrupted its heap memory area (address zero)!'); + } +} // end include: runtime_stack_check.js var __ATPRERUN__ = []; // functions called before the runtime is initialized var __ATINIT__ = []; // functions called during startup @@ -296,8 +391,11 @@ function preRun() { } function initRuntime() { + assert(!runtimeInitialized); runtimeInitialized = true; + checkStackCookie(); + if (!Module['noFSInit'] && !FS.initialized) FS.init(); @@ -308,6 +406,7 @@ TTY.init(); } function postRun() { + checkStackCookie(); if (Module['postRun']) { if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; @@ -343,6 +442,10 @@ function addOnPostRun(cb) { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc +assert(Math.imul, 'This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.fround, 'This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.clz32, 'This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.trunc, 'This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); // end include: runtime_math.js // A counter of dependencies for calling run(). If we need to // do asynchronous work before running, increment this and @@ -353,9 +456,15 @@ function addOnPostRun(cb) { // the dependencies are met. var runDependencies = 0; var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled +var runDependencyTracking = {}; +var runDependencyWatcher = null; function getUniqueRunDependency(id) { - return id; + var orig = id; + while (1) { + if (!runDependencyTracking[id]) return id; + id = orig + Math.random(); + } } function addRunDependency(id) { @@ -363,6 +472,33 @@ function addRunDependency(id) { Module['monitorRunDependencies']?.(runDependencies); + if (id) { + assert(!runDependencyTracking[id]); + runDependencyTracking[id] = 1; + if (runDependencyWatcher === null && typeof setInterval != 'undefined') { + // Check for missing dependencies every few seconds + runDependencyWatcher = setInterval(() => { + if (ABORT) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + return; + } + var shown = false; + for (var dep in runDependencyTracking) { + if (!shown) { + shown = true; + err('still waiting on run dependencies:'); + } + err(`dependency: ${dep}`); + } + if (shown) { + err('(end of list)'); + } + }, 10000); + } + } else { + err('warning: run dependency added without ID'); + } } function removeRunDependency(id) { @@ -370,7 +506,17 @@ function removeRunDependency(id) { Module['monitorRunDependencies']?.(runDependencies); + if (id) { + assert(runDependencyTracking[id]); + delete runDependencyTracking[id]; + } else { + err('warning: run dependency removed without ID'); + } if (runDependencies == 0) { + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } if (dependenciesFulfilled) { var callback = dependenciesFulfilled; dependenciesFulfilled = null; @@ -390,8 +536,6 @@ function abort(what) { ABORT = true; - what += '. Build with -sASSERTIONS for more info.'; - // Use a wasm runtime error, because a JS error might be seen as a foreign // exception, which means we'd run destructors on it. We need the error to // simply make the program stop. @@ -432,6 +576,17 @@ var isDataURI = (filename) => filename.startsWith(dataURIPrefix); */ var isFileURI = (filename) => filename.startsWith('file://'); // end include: URIUtils.js +function createExportWrapper(name, nargs) { + return (...args) => { + assert(runtimeInitialized, `native function \`${name}\` called before runtime initialization`); + var f = wasmExports[name]; + assert(f, `exported native function \`${name}\` not found`); + // Only assert for too many arguments. Too few can be valid since the missing arguments will be zero filled. + assert(args.length <= nargs, `native function \`${name}\` called with ${args.length} args but expects ${nargs}`); + return f(...args); + }; +} + // include: runtime_exceptions.js // end include: runtime_exceptions.js function findWasmBinary() { @@ -479,6 +634,10 @@ async function instantiateArrayBuffer(binaryFile, imports) { } catch (reason) { err(`failed to asynchronously prepare wasm: ${reason}`); + // Warn on some common problems. + if (isFileURI(wasmBinaryFile)) { + err(`warning: Loading from a file URI (${wasmBinaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`); + } abort(reason); } } @@ -534,6 +693,7 @@ async function createWasm() { wasmMemory = wasmExports['memory']; + assert(wasmMemory, 'memory not found in wasm exports'); updateMemoryViews(); addOnInit(wasmExports['__wasm_call_ctors']); @@ -545,9 +705,15 @@ async function createWasm() { addRunDependency('wasm-instantiate'); // Prefer streaming instantiation if available. + // Async compilation can be confusing when an error on the page overwrites Module + // (for example, if the order of elements is wrong, and the one defining Module is + // later), so we save Module and check it later. + var trueModule = Module; function receiveInstantiationResult(result) { // 'result' is a ResultObject object which has both the module and instance. // receiveInstance() will swap in the exports (to Module.asm) so they can be called + assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'); + trueModule = null; // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. // When the regression is fixed, can restore the above PTHREADS-enabled path. receiveInstance(result['instance']); @@ -578,6 +744,121 @@ async function createWasm() { } // include: runtime_debug.js +// Endianness check +(() => { + var h16 = new Int16Array(1); + var h8 = new Int8Array(h16.buffer); + h16[0] = 0x6373; + if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)'; +})(); + +if (Module['ENVIRONMENT']) { + throw new Error('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'); +} + +function legacyModuleProp(prop, newName, incoming=true) { + if (!Object.getOwnPropertyDescriptor(Module, prop)) { + Object.defineProperty(Module, prop, { + configurable: true, + get() { + let extra = incoming ? ' (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)' : ''; + abort(`\`Module.${prop}\` has been replaced by \`${newName}\`` + extra); + + } + }); + } +} + +function ignoredModuleProp(prop) { + if (Object.getOwnPropertyDescriptor(Module, prop)) { + abort(`\`Module.${prop}\` was supplied but \`${prop}\` not included in INCOMING_MODULE_JS_API`); + } +} + +// forcing the filesystem exports a few things by default +function isExportedByForceFilesystem(name) { + return name === 'FS_createPath' || + name === 'FS_createDataFile' || + name === 'FS_createPreloadedFile' || + name === 'FS_unlink' || + name === 'addRunDependency' || + // The old FS has some functionality that WasmFS lacks. + name === 'FS_createLazyFile' || + name === 'FS_createDevice' || + name === 'removeRunDependency'; +} + +/** + * Intercept access to a global symbol. This enables us to give informative + * warnings/errors when folks attempt to use symbols they did not include in + * their build, or no symbols that no longer exist. + */ +function hookGlobalSymbolAccess(sym, func) { + if (typeof globalThis != 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) { + Object.defineProperty(globalThis, sym, { + configurable: true, + get() { + func(); + return undefined; + } + }); + } +} + +function missingGlobal(sym, msg) { + hookGlobalSymbolAccess(sym, () => { + warnOnce(`\`${sym}\` is not longer defined by emscripten. ${msg}`); + }); +} + +missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); +missingGlobal('asm', 'Please use wasmExports instead'); + +function missingLibrarySymbol(sym) { + hookGlobalSymbolAccess(sym, () => { + // Can't `abort()` here because it would break code that does runtime + // checks. e.g. `if (typeof SDL === 'undefined')`. + var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`; + // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in + // library.js, which means $name for a JS name with no prefix, or name + // for a JS name like _name. + var librarySymbol = sym; + if (!librarySymbol.startsWith('_')) { + librarySymbol = '$' + sym; + } + msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + warnOnce(msg); + }); + + // Any symbol that is not included from the JS library is also (by definition) + // not exported on the Module object. + unexportedRuntimeSymbol(sym); +} + +function unexportedRuntimeSymbol(sym) { + if (!Object.getOwnPropertyDescriptor(Module, sym)) { + Object.defineProperty(Module, sym, { + configurable: true, + get() { + var msg = `'${sym}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + abort(msg); + } + }); + } +} + +// Used by XXXXX_DEBUG settings to output debug messages. +function dbg(...args) { + // TODO(sbc): Make this configurable somehow. Its not always convenient for + // logging to show up as warnings. + console.warn(...args); +} // end include: runtime_debug.js // === Body === // end include: preamble.js @@ -620,6 +901,13 @@ async function createWasm() { var noExitRuntime = Module['noExitRuntime'] || true; + var ptrToString = (ptr) => { + assert(typeof ptr === 'number'); + // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. + ptr >>>= 0; + return '0x' + ptr.toString(16).padStart(8, '0'); + }; + /** * @param {number} ptr @@ -645,6 +933,92 @@ async function createWasm() { var stackSave = () => _emscripten_stack_get_current(); + var warnOnce = (text) => { + warnOnce.shown ||= {}; + if (!warnOnce.shown[text]) { + warnOnce.shown[text] = 1; + if (ENVIRONMENT_IS_NODE) text = 'warning: ' + text; + err(text); + } + }; + + var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder() : undefined; + + /** + * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given + * array that contains uint8 values, returns a copy of that string as a + * Javascript String object. + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number=} idx + * @param {number=} maxBytesToRead + * @return {string} + */ + var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => { + var endIdx = idx + maxBytesToRead; + var endPtr = idx; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. Also, use the length info to avoid running tiny + // strings through TextDecoder, since .subarray() allocates garbage. + // (As a tiny code save trick, compare endPtr against endIdx using a negation, + // so that undefined/NaN means Infinity) + while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; + + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ''; + // If building with TextDecoder, we have already computed the string length + // above, so test loop end condition against that + while (idx < endPtr) { + // For UTF8 byte structure, see: + // http://en.wikipedia.org/wiki/UTF-8#Description + // https://www.ietf.org/rfc/rfc2279.txt + // https://tools.ietf.org/html/rfc3629 + var u0 = heapOrArray[idx++]; + if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } + var u1 = heapOrArray[idx++] & 63; + if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } + var u2 = heapOrArray[idx++] & 63; + if ((u0 & 0xF0) == 0xE0) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte ' + ptrToString(u0) + ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!'); + u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); + } + + if (u0 < 0x10000) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 0x10000; + str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); + } + } + return str; + }; + + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using one + * @return {string} + */ + var UTF8ToString = (ptr, maxBytesToRead) => { + assert(typeof ptr == 'number', `UTF8ToString expects a number (got ${typeof ptr})`); + return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; + }; + Module['UTF8ToString'] = UTF8ToString; + var ___assert_fail = (condition, filename, line, func) => + abort(`Assertion failed: ${UTF8ToString(condition)}, at: ` + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']); + class ExceptionInfo { // excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it. constructor(excPtr) { @@ -711,13 +1085,14 @@ async function createWasm() { info.init(type, destructor); exceptionLast = ptr; uncaughtExceptionCount++; - throw exceptionLast; + assert(false, 'Exception thrown, but exception catching is not enabled. Compile with -sNO_DISABLE_EXCEPTION_CATCHING or -sEXCEPTION_CATCHING_ALLOWED=[..] to catch.'); }; var __abort_js = () => - abort(''); + abort('native code called abort()'); var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { + assert(typeof str === 'string', `stringToUTF8Array expects a string (got ${typeof str})`); // Parameter maxBytesToWrite is not optional. Negative values, 0, null, // undefined and false each don't write out any bytes. if (!(maxBytesToWrite > 0)) @@ -752,6 +1127,7 @@ async function createWasm() { heap[outIdx++] = 0x80 | (u & 63); } else { if (outIdx + 3 >= endIdx) break; + if (u > 0x10FFFF) warnOnce('Invalid Unicode code point ' + ptrToString(u) + ' encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF).'); heap[outIdx++] = 0xF0 | (u >> 18); heap[outIdx++] = 0x80 | ((u >> 12) & 63); heap[outIdx++] = 0x80 | ((u >> 6) & 63); @@ -763,9 +1139,32 @@ async function createWasm() { return outIdx - startIdx; }; var stringToUTF8 = (str, outPtr, maxBytesToWrite) => { + assert(typeof maxBytesToWrite == 'number', 'stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!'); return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); }; Module['stringToUTF8'] = stringToUTF8; + + var lengthBytesUTF8 = (str) => { + var len = 0; + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code + // unit, not a Unicode code point of the character! So decode + // UTF16->UTF32->UTF8. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + var c = str.charCodeAt(i); // possibly a lead surrogate + if (c <= 0x7F) { + len++; + } else if (c <= 0x7FF) { + len += 2; + } else if (c >= 0xD800 && c <= 0xDFFF) { + len += 4; ++i; + } else { + len += 3; + } + } + return len; + }; + Module['lengthBytesUTF8'] = lengthBytesUTF8; var __tzset_js = (timezone, daylight, std_name, dst_name) => { // TODO: Use (malleable) environment variables instead of system settings. var currentYear = new Date().getFullYear(); @@ -805,6 +1204,10 @@ async function createWasm() { var winterName = extractZone(winterOffset); var summerName = extractZone(summerOffset); + assert(winterName); + assert(summerName); + assert(lengthBytesUTF8(winterName) <= 16, `timezone name truncated to fit in TZNAME_MAX (${winterName})`); + assert(lengthBytesUTF8(summerName) <= 16, `timezone name truncated to fit in TZNAME_MAX (${summerName})`); if (summerOffset < winterOffset) { // Northern hemisphere stringToUTF8(winterName, std_name, 17); @@ -823,6 +1226,7 @@ async function createWasm() { 2147483648; var alignMemory = (size, alignment) => { + assert(alignment, "alignment argument is required"); return Math.ceil(size / alignment) * alignment; }; @@ -835,6 +1239,7 @@ async function createWasm() { updateMemoryViews(); return 1 /*success*/; } catch(e) { + err(`growMemory: Attempted to grow heap from ${b.byteLength} bytes to ${size} bytes, but got error: ${e}`); } // implicit 0 return to save code size (caller will cast "undefined" into 0 // anyhow) @@ -845,6 +1250,7 @@ async function createWasm() { requestedSize >>>= 0; // With multithreaded builds, races can happen (another thread might increase the size // in between), so return a failure, and let the caller retry. + assert(requestedSize > oldSize); // Memory resize rules: // 1. Always increase heap size to at least the requested size, rounded up @@ -867,6 +1273,7 @@ async function createWasm() { // (the wasm binary specifies it, so if we tried, we'd fail anyhow). var maxHeapSize = getHeapMax(); if (requestedSize > maxHeapSize) { + err(`Cannot enlarge memory, requested ${requestedSize} bytes, but the limit is ${maxHeapSize} bytes!`); return false; } @@ -886,6 +1293,7 @@ async function createWasm() { return true; } } + err(`Failed to grow the heap from ${oldSize} bytes to ${newSize} bytes, not enough memory!`); return false; }; @@ -926,6 +1334,7 @@ async function createWasm() { var stringToAscii = (str, buffer) => { for (var i = 0; i < str.length; ++i) { + assert(str.charCodeAt(i) === (str.charCodeAt(i) & 0xff)); HEAP8[buffer++] = str.charCodeAt(i); } // Null-terminate the string @@ -1046,7 +1455,7 @@ async function createWasm() { } } // we couldn't find a proper implementation, as Math.random() is not suitable for /dev/random, see emscripten-core/emscripten/pull/7096 - abort('initRandomDevice'); + abort('no cryptographic support found for randomDevice. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: (array) => { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };'); }; var randomFill = (view) => { // Lazily init on the first invocation. @@ -1110,82 +1519,9 @@ async function createWasm() { }; - var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder() : undefined; - - /** - * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given - * array that contains uint8 values, returns a copy of that string as a - * Javascript String object. - * heapOrArray is either a regular array, or a JavaScript typed array view. - * @param {number=} idx - * @param {number=} maxBytesToRead - * @return {string} - */ - var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => { - var endIdx = idx + maxBytesToRead; - var endPtr = idx; - // TextDecoder needs to know the byte length in advance, it doesn't stop on - // null terminator by itself. Also, use the length info to avoid running tiny - // strings through TextDecoder, since .subarray() allocates garbage. - // (As a tiny code save trick, compare endPtr against endIdx using a negation, - // so that undefined/NaN means Infinity) - while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; - - if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { - return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); - } - var str = ''; - // If building with TextDecoder, we have already computed the string length - // above, so test loop end condition against that - while (idx < endPtr) { - // For UTF8 byte structure, see: - // http://en.wikipedia.org/wiki/UTF-8#Description - // https://www.ietf.org/rfc/rfc2279.txt - // https://tools.ietf.org/html/rfc3629 - var u0 = heapOrArray[idx++]; - if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } - var u1 = heapOrArray[idx++] & 63; - if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } - var u2 = heapOrArray[idx++] & 63; - if ((u0 & 0xF0) == 0xE0) { - u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; - } else { - u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); - } - - if (u0 < 0x10000) { - str += String.fromCharCode(u0); - } else { - var ch = u0 - 0x10000; - str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); - } - } - return str; - }; var FS_stdin_getChar_buffer = []; - var lengthBytesUTF8 = (str) => { - var len = 0; - for (var i = 0; i < str.length; ++i) { - // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code - // unit, not a Unicode code point of the character! So decode - // UTF16->UTF32->UTF8. - // See http://unicode.org/faq/utf_bom.html#utf16-3 - var c = str.charCodeAt(i); // possibly a lead surrogate - if (c <= 0x7F) { - len++; - } else if (c <= 0x7FF) { - len += 2; - } else if (c >= 0xD800 && c <= 0xDFFF) { - len += 4; ++i; - } else { - len += 3; - } - } - return len; - }; - Module['lengthBytesUTF8'] = lengthBytesUTF8; /** @type {function(string, boolean=, number=)} */ function intArrayFromString(stringy, dontAddNull, length) { @@ -1391,7 +1727,7 @@ async function createWasm() { }; var mmapAlloc = (size) => { - abort(); + abort('internal error: mmapAlloc called but `emscripten_builtin_memalign` native symbol not exported'); }; var MEMFS = { ops_table:null, @@ -1550,7 +1886,7 @@ async function createWasm() { } }, lookup(parent, name) { - throw MEMFS.doesNotExistError; + throw new FS.ErrnoError(44); }, mknod(parent, name, mode, dev) { return MEMFS.createNode(parent, name, mode, dev); @@ -1607,6 +1943,7 @@ async function createWasm() { var contents = stream.node.contents; if (position >= stream.node.usedBytes) return 0; var size = Math.min(stream.node.usedBytes - position, length); + assert(size >= 0); if (size > 8 && contents.subarray) { // non-trivial, and typed array buffer.set(contents.subarray(position, position + size), offset); } else { @@ -1615,6 +1952,8 @@ async function createWasm() { return size; }, write(stream, buffer, offset, length, position, canOwn) { + // The data buffer should be a typed array view + assert(!(buffer instanceof ArrayBuffer)); // If the buffer is located in main memory (HEAP), and if // memory can grow, we can't hold on to references of the // memory buffer, as they may get invalidated. That means we @@ -1629,6 +1968,7 @@ async function createWasm() { if (buffer.subarray && (!node.contents || node.contents.subarray)) { // This write is from a typed array to a typed array? if (canOwn) { + assert(position === 0, 'canOwn must imply no weird position inside the file'); node.contents = buffer.subarray(offset, offset + length); node.usedBytes = length; return length; @@ -1716,6 +2056,7 @@ async function createWasm() { var asyncLoad = async (url) => { var arrayBuffer = await readAsync(url); + assert(arrayBuffer, `Loading data file "${url}" failed (no arrayBuffer).`); return new Uint8Array(arrayBuffer); }; @@ -1794,6 +2135,134 @@ async function createWasm() { + + + + var strError = (errno) => UTF8ToString(_strerror(errno)); + + var ERRNO_CODES = { + 'EPERM': 63, + 'ENOENT': 44, + 'ESRCH': 71, + 'EINTR': 27, + 'EIO': 29, + 'ENXIO': 60, + 'E2BIG': 1, + 'ENOEXEC': 45, + 'EBADF': 8, + 'ECHILD': 12, + 'EAGAIN': 6, + 'EWOULDBLOCK': 6, + 'ENOMEM': 48, + 'EACCES': 2, + 'EFAULT': 21, + 'ENOTBLK': 105, + 'EBUSY': 10, + 'EEXIST': 20, + 'EXDEV': 75, + 'ENODEV': 43, + 'ENOTDIR': 54, + 'EISDIR': 31, + 'EINVAL': 28, + 'ENFILE': 41, + 'EMFILE': 33, + 'ENOTTY': 59, + 'ETXTBSY': 74, + 'EFBIG': 22, + 'ENOSPC': 51, + 'ESPIPE': 70, + 'EROFS': 69, + 'EMLINK': 34, + 'EPIPE': 64, + 'EDOM': 18, + 'ERANGE': 68, + 'ENOMSG': 49, + 'EIDRM': 24, + 'ECHRNG': 106, + 'EL2NSYNC': 156, + 'EL3HLT': 107, + 'EL3RST': 108, + 'ELNRNG': 109, + 'EUNATCH': 110, + 'ENOCSI': 111, + 'EL2HLT': 112, + 'EDEADLK': 16, + 'ENOLCK': 46, + 'EBADE': 113, + 'EBADR': 114, + 'EXFULL': 115, + 'ENOANO': 104, + 'EBADRQC': 103, + 'EBADSLT': 102, + 'EDEADLOCK': 16, + 'EBFONT': 101, + 'ENOSTR': 100, + 'ENODATA': 116, + 'ETIME': 117, + 'ENOSR': 118, + 'ENONET': 119, + 'ENOPKG': 120, + 'EREMOTE': 121, + 'ENOLINK': 47, + 'EADV': 122, + 'ESRMNT': 123, + 'ECOMM': 124, + 'EPROTO': 65, + 'EMULTIHOP': 36, + 'EDOTDOT': 125, + 'EBADMSG': 9, + 'ENOTUNIQ': 126, + 'EBADFD': 127, + 'EREMCHG': 128, + 'ELIBACC': 129, + 'ELIBBAD': 130, + 'ELIBSCN': 131, + 'ELIBMAX': 132, + 'ELIBEXEC': 133, + 'ENOSYS': 52, + 'ENOTEMPTY': 55, + 'ENAMETOOLONG': 37, + 'ELOOP': 32, + 'EOPNOTSUPP': 138, + 'EPFNOSUPPORT': 139, + 'ECONNRESET': 15, + 'ENOBUFS': 42, + 'EAFNOSUPPORT': 5, + 'EPROTOTYPE': 67, + 'ENOTSOCK': 57, + 'ENOPROTOOPT': 50, + 'ESHUTDOWN': 140, + 'ECONNREFUSED': 14, + 'EADDRINUSE': 3, + 'ECONNABORTED': 13, + 'ENETUNREACH': 40, + 'ENETDOWN': 38, + 'ETIMEDOUT': 73, + 'EHOSTDOWN': 142, + 'EHOSTUNREACH': 23, + 'EINPROGRESS': 26, + 'EALREADY': 7, + 'EDESTADDRREQ': 17, + 'EMSGSIZE': 35, + 'EPROTONOSUPPORT': 66, + 'ESOCKTNOSUPPORT': 137, + 'EADDRNOTAVAIL': 4, + 'ENETRESET': 39, + 'EISCONN': 30, + 'ENOTCONN': 53, + 'ETOOMANYREFS': 141, + 'EUSERS': 136, + 'EDQUOT': 19, + 'ESTALE': 72, + 'ENOTSUP': 138, + 'ENOMEDIUM': 148, + 'EILSEQ': 25, + 'EOVERFLOW': 61, + 'ECANCELED': 11, + 'ENOTRECOVERABLE': 56, + 'EOWNERDEAD': 62, + 'ESTRPIPE': 135, + }; var FS = { root:null, mounts:[], @@ -1805,7 +2274,7 @@ async function createWasm() { currentPath:"/", initialized:false, ignorePermissions:true, - ErrnoError:class { + ErrnoError:class extends Error { name = 'ErrnoError'; // We set the `name` property to be able to identify `FS.ErrnoError` // - the `name` is a standard ECMA-262 property of error objects. Kind of good to have it anyway. @@ -1814,7 +2283,14 @@ async function createWasm() { // the test `err instanceof FS.ErrnoError` won't detect an error coming from another filesystem, causing bugs. // we'll use the reliable test `err.name == "ErrnoError"` instead constructor(errno) { + super(runtimeInitialized ? strError(errno) : ''); this.errno = errno; + for (var key in ERRNO_CODES) { + if (ERRNO_CODES[key] === errno) { + this.code = key; + break; + } + } } }, filesystems:null, @@ -2010,6 +2486,7 @@ async function createWasm() { return FS.lookup(parent, name); }, createNode(parent, name, mode, rdev) { + assert(typeof parent == 'object') var node = new FS.FSNode(parent, name, mode, rdev); FS.hashAddNode(node); @@ -2142,6 +2619,7 @@ async function createWasm() { }, getStream:(fd) => FS.streams[fd], createStream(stream, fd = -1) { + assert(fd >= -1); // clone it, so we can return an instance of FSStream stream = Object.assign(new FS.FSStream(), stream); @@ -2209,6 +2687,7 @@ async function createWasm() { var completed = 0; function doCallback(errCode) { + assert(FS.syncFSRequests > 0); FS.syncFSRequests--; return callback(errCode); } @@ -2235,6 +2714,11 @@ async function createWasm() { }); }, mount(type, opts, mountpoint) { + if (typeof type == 'string') { + // The filesystem was not included, and instead we have an error + // message stored in the variable. + throw type; + } var root = mountpoint === '/'; var pseudo = !mountpoint; var node; @@ -2313,6 +2797,7 @@ async function createWasm() { // remove this mount from the child mounts var idx = node.mount.mounts.indexOf(mount); + assert(idx !== -1); node.mount.mounts.splice(idx, 1); }, lookup(parent, name) { @@ -2779,6 +3264,7 @@ async function createWasm() { return stream.position; }, read(stream, buffer, offset, length, position) { + assert(offset >= 0); if (length < 0 || position < 0) { throw new FS.ErrnoError(28); } @@ -2805,6 +3291,7 @@ async function createWasm() { return bytesRead; }, write(stream, buffer, offset, length, position, canOwn) { + assert(offset >= 0); if (length < 0 || position < 0) { throw new FS.ErrnoError(28); } @@ -2876,6 +3363,7 @@ async function createWasm() { return stream.stream_ops.mmap(stream, length, position, prot, flags); }, msync(stream, buffer, offset, length, mmapFlags) { + assert(offset >= 0); if (!stream.stream_ops.msync) { return 0; } @@ -3038,6 +3526,9 @@ async function createWasm() { var stdin = FS.open('/dev/stdin', 0); var stdout = FS.open('/dev/stdout', 1); var stderr = FS.open('/dev/stderr', 1); + assert(stdin.fd === 0, `invalid handle for stdin (${stdin.fd})`); + assert(stdout.fd === 1, `invalid handle for stdout (${stdout.fd})`); + assert(stderr.fd === 2, `invalid handle for stderr (${stderr.fd})`); }, staticInit() { FS.nameTable = new Array(4096); @@ -3053,6 +3544,7 @@ async function createWasm() { }; }, init(input, output, error) { + assert(!FS.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); FS.initialized = true; // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here @@ -3065,6 +3557,7 @@ async function createWasm() { quit() { FS.initialized = false; // force-flush all streams, so we get musl std streams printed out + _fflush(0); // close all of our streams for (var i = 0; i < FS.streams.length; i++) { var stream = FS.streams[i]; @@ -3351,6 +3844,7 @@ async function createWasm() { if (position >= contents.length) return 0; var size = Math.min(contents.length - position, length); + assert(size >= 0); if (contents.slice) { // normal array for (var i = 0; i < size; i++) { buffer[offset + i] = contents[position + i]; @@ -3380,28 +3874,26 @@ async function createWasm() { node.stream_ops = stream_ops; return node; }, + absolutePath() { + abort('FS.absolutePath has been removed; use PATH_FS.resolve instead'); + }, + createFolder() { + abort('FS.createFolder has been removed; use FS.mkdir instead'); + }, + createLink() { + abort('FS.createLink has been removed; use FS.symlink instead'); + }, + joinPath() { + abort('FS.joinPath has been removed; use PATH.join instead'); + }, + mmapAlloc() { + abort('FS.mmapAlloc has been replaced by the top level function mmapAlloc'); + }, + standardizePath() { + abort('FS.standardizePath has been removed; use PATH.normalize instead'); + }, }; - - /** - * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the - * emscripten HEAP, returns a copy of that string as a Javascript String object. - * - * @param {number} ptr - * @param {number=} maxBytesToRead - An optional length that specifies the - * maximum number of bytes to read. You can omit this parameter to scan the - * string until the first 0 byte. If maxBytesToRead is passed, and the string - * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the - * string will cut short at that byte index (i.e. maxBytesToRead will not - * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing - * frequent uses of UTF8ToString() with and without maxBytesToRead may throw - * JS JIT optimizations off, so it is worth to consider consistently using one - * @return {string} - */ - var UTF8ToString = (ptr, maxBytesToRead) => { - return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; - }; - Module['UTF8ToString'] = UTF8ToString; var SYSCALLS = { DEFAULT_POLLMASK:5, calculateAt(dirfd, path, allowEmpty) { @@ -3569,12 +4061,25 @@ async function createWasm() { } } + function _random_get(buffer, size) { + try { + + randomFill(HEAPU8.subarray(buffer, buffer + size)); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + var getCFunc = (ident) => { var func = Module['_' + ident]; // closure exported function + assert(func, 'Cannot call unknown function ' + ident + ', make sure it is exported'); return func; }; var writeArrayToMemory = (array, buffer) => { + assert(array.length >= 0, 'writeArrayToMemory array must have a length (should be an array or typed array)') HEAP8.set(array, buffer); }; @@ -3626,6 +4131,7 @@ async function createWasm() { var func = getCFunc(ident); var cArgs = []; var stack = 0; + assert(returnType !== 'array', 'Return type should not be "array".'); if (args) { for (var i = 0; i < args.length; i++) { var converter = toC[argTypes[i]]; @@ -3655,13 +4161,6 @@ async function createWasm() { * @param {Object=} opts */ var cwrap = (ident, returnType, argTypes, opts) => { - // When the function takes numbers and returns a number, we can just return - // the original function - var numericArgs = !argTypes || argTypes.every((type) => type === 'number' || type === 'boolean'); - var numericRet = returnType !== 'string'; - if (numericRet && numericArgs && !opts) { - return getCFunc(ident); - } return (...args) => ccall(ident, returnType, argTypes, args, opts); }; @@ -3672,14 +4171,12 @@ async function createWasm() { FS.staticInit(); // Set module methods based on EXPORTED_RUNTIME_METHODS ; - - // This error may happen quite a bit. To avoid overhead we reuse it (and - // suffer a lack of stack info). - MEMFS.doesNotExistError = new FS.ErrnoError(44); - /** @suppress {checkTypes} */ - MEMFS.doesNotExistError.stack = ''; - ; +function checkIncomingModuleAPI() { + ignoredModuleProp('fetchSettings'); +} var wasmImports = { + /** @export */ + __assert_fail: ___assert_fail, /** @export */ __cxa_throw: ___cxa_throw, /** @export */ @@ -3699,26 +4196,36 @@ var wasmImports = { /** @export */ fd_seek: _fd_seek, /** @export */ - fd_write: _fd_write + fd_write: _fd_write, + /** @export */ + random_get: _random_get }; var wasmExports; createWasm(); -var ___wasm_call_ctors = () => (___wasm_call_ctors = wasmExports['__wasm_call_ctors'])(); -var _rainstormHash64 = Module['_rainstormHash64'] = (a0, a1, a2, a3) => (_rainstormHash64 = Module['_rainstormHash64'] = wasmExports['rainstormHash64'])(a0, a1, a2, a3); -var _rainstormHash128 = Module['_rainstormHash128'] = (a0, a1, a2, a3) => (_rainstormHash128 = Module['_rainstormHash128'] = wasmExports['rainstormHash128'])(a0, a1, a2, a3); -var _rainstormHash256 = Module['_rainstormHash256'] = (a0, a1, a2, a3) => (_rainstormHash256 = Module['_rainstormHash256'] = wasmExports['rainstormHash256'])(a0, a1, a2, a3); -var _rainstormHash512 = Module['_rainstormHash512'] = (a0, a1, a2, a3) => (_rainstormHash512 = Module['_rainstormHash512'] = wasmExports['rainstormHash512'])(a0, a1, a2, a3); -var _rainbowHash64 = Module['_rainbowHash64'] = (a0, a1, a2, a3) => (_rainbowHash64 = Module['_rainbowHash64'] = wasmExports['rainbowHash64'])(a0, a1, a2, a3); -var _rainbowHash128 = Module['_rainbowHash128'] = (a0, a1, a2, a3) => (_rainbowHash128 = Module['_rainbowHash128'] = wasmExports['rainbowHash128'])(a0, a1, a2, a3); -var _rainbowHash256 = Module['_rainbowHash256'] = (a0, a1, a2, a3) => (_rainbowHash256 = Module['_rainbowHash256'] = wasmExports['rainbowHash256'])(a0, a1, a2, a3); -var _wasmGetFileHeaderInfo = Module['_wasmGetFileHeaderInfo'] = (a0, a1) => (_wasmGetFileHeaderInfo = Module['_wasmGetFileHeaderInfo'] = wasmExports['wasmGetFileHeaderInfo'])(a0, a1); -var _malloc = Module['_malloc'] = (a0) => (_malloc = Module['_malloc'] = wasmExports['malloc'])(a0); -var _wasmFree = Module['_wasmFree'] = (a0) => (_wasmFree = Module['_wasmFree'] = wasmExports['wasmFree'])(a0); -var _free = Module['_free'] = (a0) => (_free = Module['_free'] = wasmExports['free'])(a0); -var _wasmStreamEncryptBuffer = Module['_wasmStreamEncryptBuffer'] = (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) => (_wasmStreamEncryptBuffer = Module['_wasmStreamEncryptBuffer'] = wasmExports['wasmStreamEncryptBuffer'])(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); -var _wasmStreamDecryptBuffer = Module['_wasmStreamDecryptBuffer'] = (a0, a1, a2, a3, a4, a5, a6, a7) => (_wasmStreamDecryptBuffer = Module['_wasmStreamDecryptBuffer'] = wasmExports['wasmStreamDecryptBuffer'])(a0, a1, a2, a3, a4, a5, a6, a7); -var _wasmFreeString = Module['_wasmFreeString'] = (a0) => (_wasmFreeString = Module['_wasmFreeString'] = wasmExports['wasmFreeString'])(a0); -var _wasmFreeBuffer = Module['_wasmFreeBuffer'] = (a0) => (_wasmFreeBuffer = Module['_wasmFreeBuffer'] = wasmExports['wasmFreeBuffer'])(a0); +var ___wasm_call_ctors = createExportWrapper('__wasm_call_ctors', 0); +var _rainstormHash64 = Module['_rainstormHash64'] = createExportWrapper('rainstormHash64', 4); +var _rainstormHash128 = Module['_rainstormHash128'] = createExportWrapper('rainstormHash128', 4); +var _rainstormHash256 = Module['_rainstormHash256'] = createExportWrapper('rainstormHash256', 4); +var _rainstormHash512 = Module['_rainstormHash512'] = createExportWrapper('rainstormHash512', 4); +var _rainbowHash64 = Module['_rainbowHash64'] = createExportWrapper('rainbowHash64', 4); +var _rainbowHash128 = Module['_rainbowHash128'] = createExportWrapper('rainbowHash128', 4); +var _rainbowHash256 = Module['_rainbowHash256'] = createExportWrapper('rainbowHash256', 4); +var _wasmGetFileHeaderInfo = Module['_wasmGetFileHeaderInfo'] = createExportWrapper('wasmGetFileHeaderInfo', 2); +var _malloc = Module['_malloc'] = createExportWrapper('malloc', 1); +var _wasmFree = Module['_wasmFree'] = createExportWrapper('wasmFree', 1); +var _free = Module['_free'] = createExportWrapper('free', 1); +var _wasmStreamEncryptBuffer = Module['_wasmStreamEncryptBuffer'] = createExportWrapper('wasmStreamEncryptBuffer', 14); +var _wasmStreamDecryptBuffer = Module['_wasmStreamDecryptBuffer'] = createExportWrapper('wasmStreamDecryptBuffer', 8); +var _wasmFreeString = Module['_wasmFreeString'] = createExportWrapper('wasmFreeString', 1); +var _wasmFreeBuffer = Module['_wasmFreeBuffer'] = createExportWrapper('wasmFreeBuffer', 1); +var _wasmBlockEncryptBuffer = Module['_wasmBlockEncryptBuffer'] = createExportWrapper('wasmBlockEncryptBuffer', 4); +var _wasmBlockDecryptBuffer = Module['_wasmBlockDecryptBuffer'] = createExportWrapper('wasmBlockDecryptBuffer', 6); +var _fflush = createExportWrapper('fflush', 1); +var _strerror = createExportWrapper('strerror', 1); +var _emscripten_stack_init = () => (_emscripten_stack_init = wasmExports['emscripten_stack_init'])(); +var _emscripten_stack_get_free = () => (_emscripten_stack_get_free = wasmExports['emscripten_stack_get_free'])(); +var _emscripten_stack_get_base = () => (_emscripten_stack_get_base = wasmExports['emscripten_stack_get_base'])(); +var _emscripten_stack_get_end = () => (_emscripten_stack_get_end = wasmExports['emscripten_stack_get_end'])(); var __emscripten_stack_restore = (a0) => (__emscripten_stack_restore = wasmExports['_emscripten_stack_restore'])(a0); var __emscripten_stack_alloc = (a0) => (__emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc'])(a0); var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmExports['emscripten_stack_get_current'])(); @@ -3730,6 +4237,290 @@ var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmE Module['wasmExports'] = wasmExports; Module['ccall'] = ccall; Module['cwrap'] = cwrap; +var missingLibrarySymbols = [ + 'writeI53ToI64', + 'writeI53ToI64Clamped', + 'writeI53ToI64Signaling', + 'writeI53ToU64Clamped', + 'writeI53ToU64Signaling', + 'readI53FromI64', + 'readI53FromU64', + 'convertI32PairToI53', + 'convertI32PairToI53Checked', + 'convertU32PairToI53', + 'getTempRet0', + 'setTempRet0', + 'exitJS', + 'inetPton4', + 'inetNtop4', + 'inetPton6', + 'inetNtop6', + 'readSockaddr', + 'writeSockaddr', + 'emscriptenLog', + 'readEmAsmArgs', + 'jstoi_q', + 'listenOnce', + 'autoResumeAudioContext', + 'getDynCaller', + 'dynCall', + 'handleException', + 'keepRuntimeAlive', + 'runtimeKeepalivePush', + 'runtimeKeepalivePop', + 'callUserCallback', + 'maybeExit', + 'asmjsMangle', + 'HandleAllocator', + 'getNativeTypeSize', + 'STACK_SIZE', + 'STACK_ALIGN', + 'POINTER_SIZE', + 'ASSERTIONS', + 'uleb128Encode', + 'sigToWasmTypes', + 'generateFuncType', + 'convertJsFunctionToWasm', + 'getEmptyTableSlot', + 'updateTableMap', + 'getFunctionAddress', + 'addFunction', + 'removeFunction', + 'reallyNegative', + 'unSign', + 'strLen', + 'reSign', + 'formatString', + 'intArrayToString', + 'AsciiToString', + 'UTF16ToString', + 'stringToUTF16', + 'lengthBytesUTF16', + 'UTF32ToString', + 'stringToUTF32', + 'lengthBytesUTF32', + 'stringToNewUTF8', + 'registerKeyEventCallback', + 'maybeCStringToJsString', + 'findEventTarget', + 'getBoundingClientRect', + 'fillMouseEventData', + 'registerMouseEventCallback', + 'registerWheelEventCallback', + 'registerUiEventCallback', + 'registerFocusEventCallback', + 'fillDeviceOrientationEventData', + 'registerDeviceOrientationEventCallback', + 'fillDeviceMotionEventData', + 'registerDeviceMotionEventCallback', + 'screenOrientation', + 'fillOrientationChangeEventData', + 'registerOrientationChangeEventCallback', + 'fillFullscreenChangeEventData', + 'registerFullscreenChangeEventCallback', + 'JSEvents_requestFullscreen', + 'JSEvents_resizeCanvasForFullscreen', + 'registerRestoreOldStyle', + 'hideEverythingExceptGivenElement', + 'restoreHiddenElements', + 'setLetterbox', + 'softFullscreenResizeWebGLRenderTarget', + 'doRequestFullscreen', + 'fillPointerlockChangeEventData', + 'registerPointerlockChangeEventCallback', + 'registerPointerlockErrorEventCallback', + 'requestPointerLock', + 'fillVisibilityChangeEventData', + 'registerVisibilityChangeEventCallback', + 'registerTouchEventCallback', + 'fillGamepadEventData', + 'registerGamepadEventCallback', + 'registerBeforeUnloadEventCallback', + 'fillBatteryEventData', + 'battery', + 'registerBatteryEventCallback', + 'setCanvasElementSize', + 'getCanvasElementSize', + 'jsStackTrace', + 'getCallstack', + 'convertPCtoSourceLocation', + 'checkWasiClock', + 'wasiRightsToMuslOFlags', + 'wasiOFlagsToMuslOFlags', + 'safeSetTimeout', + 'setImmediateWrapped', + 'safeRequestAnimationFrame', + 'clearImmediateWrapped', + 'polyfillSetImmediate', + 'registerPostMainLoop', + 'registerPreMainLoop', + 'getPromise', + 'makePromise', + 'idsToPromises', + 'makePromiseCallback', + 'findMatchingCatch', + 'Browser_asyncPrepareDataCounter', + 'isLeapYear', + 'ydayFromDate', + 'arraySum', + 'addDays', + 'getSocketFromFD', + 'getSocketAddress', + 'FS_unlink', + 'FS_mkdirTree', + '_setNetworkCallback', + 'heapObjectForWebGLType', + 'toTypedArrayIndex', + 'webgl_enable_ANGLE_instanced_arrays', + 'webgl_enable_OES_vertex_array_object', + 'webgl_enable_WEBGL_draw_buffers', + 'webgl_enable_WEBGL_multi_draw', + 'webgl_enable_EXT_polygon_offset_clamp', + 'webgl_enable_EXT_clip_control', + 'webgl_enable_WEBGL_polygon_mode', + 'emscriptenWebGLGet', + 'computeUnpackAlignedImageSize', + 'colorChannelsInGlTextureFormat', + 'emscriptenWebGLGetTexPixelData', + 'emscriptenWebGLGetUniform', + 'webglGetUniformLocation', + 'webglPrepareUniformLocationsBeforeFirstUse', + 'webglGetLeftBracePos', + 'emscriptenWebGLGetVertexAttrib', + '__glGetActiveAttribOrUniform', + 'writeGLArray', + 'registerWebGlEventCallback', + 'runAndAbortIfError', + 'ALLOC_NORMAL', + 'ALLOC_STACK', + 'allocate', + 'writeStringToMemory', + 'writeAsciiToMemory', + 'setErrNo', + 'demangle', + 'stackTrace', +]; +missingLibrarySymbols.forEach(missingLibrarySymbol) + +var unexportedSymbols = [ + 'run', + 'addOnPreRun', + 'addOnInit', + 'addOnPreMain', + 'addOnExit', + 'addOnPostRun', + 'addRunDependency', + 'removeRunDependency', + 'out', + 'err', + 'callMain', + 'abort', + 'wasmMemory', + 'writeStackCookie', + 'checkStackCookie', + 'INT53_MAX', + 'INT53_MIN', + 'bigintToI53Checked', + 'stackSave', + 'stackRestore', + 'stackAlloc', + 'ptrToString', + 'zeroMemory', + 'getHeapMax', + 'growMemory', + 'ENV', + 'ERRNO_CODES', + 'strError', + 'DNS', + 'Protocols', + 'Sockets', + 'timers', + 'warnOnce', + 'readEmAsmArgsArray', + 'jstoi_s', + 'getExecutableName', + 'asyncLoad', + 'alignMemory', + 'mmapAlloc', + 'wasmTable', + 'noExitRuntime', + 'getCFunc', + 'freeTableIndexes', + 'functionsInTableMap', + 'setValue', + 'getValue', + 'PATH', + 'PATH_FS', + 'UTF8Decoder', + 'UTF8ArrayToString', + 'UTF8ToString', + 'stringToUTF8Array', + 'stringToUTF8', + 'lengthBytesUTF8', + 'intArrayFromString', + 'stringToAscii', + 'UTF16Decoder', + 'stringToUTF8OnStack', + 'writeArrayToMemory', + 'JSEvents', + 'specialHTMLTargets', + 'findCanvasEventTarget', + 'currentFullscreenStrategy', + 'restoreOldWindowedStyle', + 'UNWIND_CACHE', + 'ExitStatus', + 'getEnvStrings', + 'doReadv', + 'doWritev', + 'initRandomFill', + 'randomFill', + 'promiseMap', + 'uncaughtExceptionCount', + 'exceptionLast', + 'exceptionCaught', + 'ExceptionInfo', + 'Browser', + 'getPreloadedImageData__data', + 'wget', + 'MONTH_DAYS_REGULAR', + 'MONTH_DAYS_LEAP', + 'MONTH_DAYS_REGULAR_CUMULATIVE', + 'MONTH_DAYS_LEAP_CUMULATIVE', + 'SYSCALLS', + 'preloadPlugins', + 'FS_createPreloadedFile', + 'FS_modeStringToFlags', + 'FS_getMode', + 'FS_stdin_getChar_buffer', + 'FS_stdin_getChar', + 'FS_createPath', + 'FS_createDevice', + 'FS_readFile', + 'FS', + 'FS_createDataFile', + 'FS_createLazyFile', + 'MEMFS', + 'TTY', + 'PIPEFS', + 'SOCKFS', + 'tempFixedLengthArray', + 'miniTempWebGLFloatBuffers', + 'miniTempWebGLIntBuffers', + 'GL', + 'AL', + 'GLUT', + 'EGL', + 'GLEW', + 'IDBStore', + 'SDL', + 'SDL_gfx', + 'allocateUTF8', + 'allocateUTF8OnStack', + 'print', + 'printErr', +]; +unexportedSymbols.forEach(unexportedRuntimeSymbol); + var calledRun; @@ -3740,12 +4531,23 @@ dependenciesFulfilled = function runCaller() { if (!calledRun) dependenciesFulfilled = runCaller; // try this again later, after new deps are fulfilled }; +function stackCheckInit() { + // This is normally called automatically during __wasm_call_ctors but need to + // get these values before even running any of the ctors so we call it redundantly + // here. + _emscripten_stack_init(); + // TODO(sbc): Move writeStackCookie to native to to avoid this. + writeStackCookie(); +} + function run() { if (runDependencies > 0) { return; } + stackCheckInit(); + preRun(); // a preRun added a dependency, run will be called later @@ -3766,6 +4568,8 @@ function run() { Module['onRuntimeInitialized']?.(); + assert(!Module['_main'], 'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]'); + postRun(); } @@ -3779,6 +4583,46 @@ function run() { { doRun(); } + checkStackCookie(); +} + +function checkUnflushedContent() { + // Compiler settings do not allow exiting the runtime, so flushing + // the streams is not possible. but in ASSERTIONS mode we check + // if there was something to flush, and if so tell the user they + // should request that the runtime be exitable. + // Normally we would not even include flush() at all, but in ASSERTIONS + // builds we do so just for this check, and here we see if there is any + // content to flush, that is, we check if there would have been + // something a non-ASSERTIONS build would have not seen. + // How we flush the streams depends on whether we are in SYSCALLS_REQUIRE_FILESYSTEM=0 + // mode (which has its own special function for this; otherwise, all + // the code is inside libc) + var oldOut = out; + var oldErr = err; + var has = false; + out = err = (x) => { + has = true; + } + try { // it doesn't matter if it fails + _fflush(0); + // also flush in the JS FS layer + ['stdout', 'stderr'].forEach((name) => { + var info = FS.analyzePath('/dev/' + name); + if (!info) return; + var stream = info.object; + var rdev = stream.rdev; + var tty = TTY.ttys[rdev]; + if (tty?.output?.length) { + has = true; + } + }); + } catch(e) {} + out = oldOut; + err = oldErr; + if (has) { + warnOnce('stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the Emscripten FAQ), or make sure to emit a newline when you printf etc.'); + } } if (Module['preInit']) { diff --git a/docs/rain.wasm b/docs/rain.wasm index 9656edf..8a0e7df 100755 Binary files a/docs/rain.wasm and b/docs/rain.wasm differ diff --git a/issue-patch b/issue-patch new file mode 100644 index 0000000..b34b4c4 --- /dev/null +++ b/issue-patch @@ -0,0 +1,1012 @@ +diff --combined src/block-cipher.h +index 0925595,bc2b99c..0000000 +--- a/src/block-cipher.h ++++ b/src/block-cipher.h +@@@ -1,12 -1,13 +1,57 @@@ + #include "parallel-scatter.h" + - // encrypt block cipher + - static void puzzleEncryptFileWithHeader( + - const std::string &inFilename, + - const std::string &outFilename, + + +++ +++/** +++ * A helper to print out the puzzle encryption parameters for debugging. +++ * This will ensure the logs appear when run under wasm. +++ */ +++static void debugPrintPuzzleParams( +++ const std::vector& plainData, ++ const std::string& key, ++ HashAlgorithm algot, ++ uint32_t hash_size, + - uint64_t seed, // Used as IV + - std::vector salt, // Provided salt +++ uint64_t seed, +++ const std::vector& salt, +++ size_t blockSize, +++ size_t nonceSize, +++ const std::string& searchMode, +++ bool verbose, +++ bool deterministicNonce, +++ uint32_t outputExtension) +++{ +++ std::cerr << "[puzzleEncryptBufferWithHeader - DEBUG]\n" +++ << " plainData.size(): " << plainData.size() << "\n" +++ << " key: \"" << key << "\" (length: " << key.size() << ")\n" +++ << " algot: " << ((algot == HashAlgorithm::Rainbow) ? "Rainbow" : +++ (algot == HashAlgorithm::Rainstorm ? "Rainstorm" : "Unknown")) << "\n" +++ << " hash_size (bits): " << hash_size << "\n" +++ << " seed (iv): " << seed << "\n" +++ << " salt.size(): " << salt.size() << "\n" +++ << " blockSize: " << blockSize << "\n" +++ << " nonceSize: " << nonceSize << "\n" +++ << " searchMode: \"" << searchMode << "\"\n" +++ << " verbose: " << (verbose ? "true" : "false") << "\n" +++ << " deterministicNonce: " << (deterministicNonce ? "true" : "false") << "\n" +++ << " outputExtension: " << outputExtension << "\n"; +++ +++ // Optionally print the first few bytes of salt to confirm +++ if (!salt.empty()) { +++ std::cerr << " Salt bytes (up to 16): "; +++ for (size_t i = 0; i < salt.size() && i < 16; i++) { +++ std::cerr << std::hex << (int)salt[i] << " "; +++ } +++ std::cerr << std::dec << "\n"; +++ } +++} +++ +++ + +static std::vector puzzleEncryptBufferWithHeader( + + const std::vector &plainData, + + const std::string &key, + + HashAlgorithm algot, + + uint32_t hash_size, + + uint64_t seed, + + const std::vector &salt, + size_t blockSize, + size_t nonceSize, + const std::string &searchMode, +@@@ -14,121 -15,147 +59,128 @@@ + bool deterministicNonce, + uint32_t outputExtension + ) { + - if (hash_size / 8 + outputExtension > 65536) { + - std::cerr << "[Warning] Total output length (" + - << hash_size / 8 + outputExtension + - << ") exceeds the cap of 65536 bytes.\n"; + - outputExtension = std::min(outputExtension, 65536 - hash_size / 8); + - std::cerr << "[Warning] Capping outputExtension to " << outputExtension << "\n" ; + - } + - // 1) Read & compress plaintext + - std::ifstream fin(inFilename, std::ios::binary); + - if (!fin.is_open()) { + - throw std::runtime_error("Cannot open input file: " + inFilename); + - } + - std::vector plainData( + - (std::istreambuf_iterator(fin)), + - (std::istreambuf_iterator()) + - ); + - fin.close(); + - + #ifdef _OPENMP + int halfCores = std::max(1, 1 + static_cast(std::thread::hardware_concurrency()) / 2); + omp_set_num_threads(halfCores); + #endif + + - auto compressed = compressData(plainData); +++ // 0) Print debug info about all parameters +++ debugPrintPuzzleParams( +++ plainData, key, algot, hash_size, seed, +++ salt, blockSize, nonceSize, searchMode, +++ verbose, deterministicNonce, outputExtension +++ ); ++ + - plainData = std::move(compressed); + + // 1) Compress plaintext + + //auto compressed = compressData(plainData); + + auto compressed = plainData; + + - // 2) Prepare output & new FileHeader + - std::ofstream fout(outFilename, std::ios::binary); + - if (!fout.is_open()) { + - throw std::runtime_error("Cannot open output file: " + outFilename); + - } + + // 2) Prepare output buffer + + std::vector outBuffer; + + outBuffer.reserve(compressed.size() + 1024); // Just a guess to reduce re-allocs + + - // 3) Determine searchModeEnum based on searchMode string + - uint8_t searchModeEnum = 0xFF; // Default for Stream Cipher Mode + - if (algot == HashAlgorithm::Rainbow || algot == HashAlgorithm::Rainstorm) { // Only for Block Cipher Modes + + // 3) Determine searchModeEnum + + uint8_t searchModeEnum = 0xFF; // Default for Stream (not used), but we'll set properly + + if (algot == HashAlgorithm::Rainbow || algot == HashAlgorithm::Rainstorm) { + if (searchMode == "prefix") { + searchModeEnum = 0x00; + - } + - else if (searchMode == "sequence") { + + } else if (searchMode == "sequence") { + searchModeEnum = 0x01; + - } + - else if (searchMode == "series") { + + } else if (searchMode == "series") { + searchModeEnum = 0x02; + - } + - else if (searchMode == "scatter") { + + } else if (searchMode == "scatter") { + searchModeEnum = 0x03; + - } + - else if (searchMode == "mapscatter") { + + } else if (searchMode == "mapscatter") { + searchModeEnum = 0x04; + - } + - else if (searchMode == "parascatter") { + + } else if (searchMode == "parascatter") { + searchModeEnum = 0x05; + - } + - else { + + } else { + throw std::runtime_error("Invalid search mode: " + searchMode); + } + } + + - // 4) Build the new FileHeader + + // 4) Build FileHeader + FileHeader hdr{}; + hdr.magic = MagicNumber; + - hdr.version = 0x02; // New version + - hdr.cipherMode = 0x11; // 0x11 for “Block Cipher + puzzle” + + hdr.version = 0x02; + + hdr.cipherMode = 0x11; // "Block Cipher + puzzle" + hdr.blockSize = static_cast(blockSize); + hdr.nonceSize = static_cast(nonceSize); + hdr.outputExtension = outputExtension; + - std::cout << "Output ext" << outputExtension << std::flush; + hdr.hashSizeBits = hash_size; + hdr.hashName = (algot == HashAlgorithm::Rainbow) ? "rainbow" : "rainstorm"; + - hdr.iv = seed; // The seed as our “IV” + - + - // 5) Assign provided salt + + hdr.iv = seed; + hdr.saltLen = static_cast(salt.size()); + hdr.salt = salt; + - + - // 6) Assign original (compressed) size + - hdr.originalSize = plainData.size(); + - + - // 7) Assign searchModeEnum + + hdr.originalSize = compressed.size(); + hdr.searchModeEnum = searchModeEnum; + + - // 8) Write the unified header + - writeFileHeader(fout, hdr); + - + - // 9) Prepare puzzle searching + - size_t totalBlocks = (hdr.originalSize + blockSize - 1) / blockSize; + - int progressInterval = 1'000'000; + - + - // 10) Derive a “master PRK” from (seed, salt, key) + - // Using your iterative KDF + + // 5) Write the header into outBuffer + + { + + // We'll mimic writeFileHeader but do it directly into outBuffer + + // (You could factor out a writeHeaderToMemory if you prefer.) + + auto writeVec = [&](const void* ptr, size_t len) { + + const uint8_t* p = static_cast(ptr); + + outBuffer.insert(outBuffer.end(), p, p + len); + + }; + + + + writeVec(&hdr.magic, sizeof(hdr.magic)); + + writeVec(&hdr.version, sizeof(hdr.version)); + + writeVec(&hdr.cipherMode, sizeof(hdr.cipherMode)); + + writeVec(&hdr.blockSize, sizeof(hdr.blockSize)); + + writeVec(&hdr.nonceSize, sizeof(hdr.nonceSize)); + + writeVec(&hdr.hashSizeBits, sizeof(hdr.hashSizeBits)); + + writeVec(&hdr.outputExtension, sizeof(hdr.outputExtension)); + + uint8_t hnLen = static_cast(hdr.hashName.size()); + + writeVec(&hnLen, sizeof(hnLen)); + + if (hnLen > 0) { + + writeVec(hdr.hashName.data(), hnLen); + + } + + writeVec(&hdr.iv, sizeof(hdr.iv)); + + writeVec(&hdr.saltLen, sizeof(hdr.saltLen)); + + if (hdr.saltLen > 0) { + + writeVec(hdr.salt.data(), hdr.saltLen); + + } + + writeVec(&hdr.searchModeEnum, sizeof(hdr.searchModeEnum)); + + writeVec(&hdr.originalSize, sizeof(hdr.originalSize)); + + // hmac left zero-initialized, if you use it + + writeVec(hdr.hmac.data(), hdr.hmac.size()); + + } + + + + // 6) Derive PRK + std::vector keyBuf(key.begin(), key.end()); + - // Convert seed (uint64_t) to vector + std::vector seed_vec(8); + for (size_t i = 0; i < 8; ++i) { + seed_vec[i] = static_cast((seed >> (i * 8)) & 0xFF); + } + + std::vector prk = derivePRK(seed_vec, salt, keyBuf, algot, hash_size); + + - std::vector prk = derivePRK( + - seed_vec, // Converted seed as vector + - salt, // Provided salt + - keyBuf, // IKM (Input Key Material) + - algot, // Hash algorithm + - hash_size // Hash size in bits + - ); + - + - // 11) Extend that PRK into subkeys for each block + - // Each subkey is hash_size/8 bytes + + // 7) Extend PRK into subkeys + + size_t totalBlocks = (hdr.originalSize + blockSize - 1) / blockSize; + size_t subkeySize = hdr.hashSizeBits / 8; + size_t totalNeeded = totalBlocks * subkeySize; + - std::vector allSubkeys = extendOutputKDF( + - prk, + - totalNeeded, + - algot, + - hdr.hashSizeBits + - ); + + std::vector allSubkeys = extendOutputKDF(prk, totalNeeded, algot, hdr.hashSizeBits); + + - // 12) Initialize RNG for non-deterministic nonce generation + + // 8) Setup for puzzle searching + std::mt19937_64 rng(std::random_device{}()); + std::uniform_int_distribution dist(0, 255); + uint64_t nonceCounter = 0; + - + size_t remaining = hdr.originalSize; + - // For mapscatter arrays + - uint8_t reverseMap[256 * 256] = {0}; + - uint8_t reverseMapOffsets[256] = {0}; + - std::bitset<256 * 256> usedIndices; + + int progressInterval = 1'000'000; + + - // 13) Iterate over each block to perform puzzle encryption + + // We'll store encryption results in outBuffer after the header + for (size_t blockIndex = 0; blockIndex < totalBlocks; blockIndex++) { + size_t thisBlockSize = std::min(blockSize, remaining); + remaining -= thisBlockSize; + + - // Extract this block + + // Extract block + std::vector block( + - plainData.begin() + blockIndex * blockSize, + - plainData.begin() + blockIndex * blockSize + thisBlockSize + + compressed.begin() + blockIndex * blockSize, + + compressed.begin() + blockIndex * blockSize + thisBlockSize + ); + + - // Extract subkey for this block + - const size_t offset = blockIndex * subkeySize; + + // Extract subkey + + size_t offset = blockIndex * subkeySize; + if (offset + subkeySize > allSubkeys.size()) { + throw std::runtime_error("Subkey index out of range."); + } +@@@ -137,50 -164,57 +189,50 @@@ + allSubkeys.begin() + offset + subkeySize + ); + + - // ----------------------- + - // parascatter branch + - // ----------------------- + - + - if (searchModeEnum == 0x05) { // parascatter + + // If parascatter + + if (searchModeEnum == 0x05) { + auto result = parallelParascatter( + - blockIndex, // Current block index + - thisBlockSize, // Size of this block + - block, // Block data + - blockSubkey, // Subkey for this block + - nonceSize, // Nonce size + - hash_size, // Hash size in bits + - seed, // Seed + - algot, // Hash algorithm + - deterministicNonce, // Deterministic nonce flag + - outputExtension // how much output is extended by per block + + blockIndex, + + thisBlockSize, + + block, + + blockSubkey, + + nonceSize, + + hash_size, + + seed, + + algot, + + deterministicNonce, + + outputExtension + ); + - + - // Write the nonce and scatter indices + - fout.write(reinterpret_cast(result.chosenNonce.data()), nonceSize); + - fout.write(reinterpret_cast(result.scatterIndices.data()), result.scatterIndices.size() * sizeof(uint16_t)); + - + - // Progress Reporting + - std::cerr << "\r[Enc] Mode: parascatter, Block " << (blockIndex + 1) + - << "/" << totalBlocks << " " << std::flush; + + // Write nonce + + outBuffer.insert(outBuffer.end(), result.chosenNonce.begin(), result.chosenNonce.end()); + + // Write scatter indices + + { + + const uint8_t* si = reinterpret_cast(result.scatterIndices.data()); + + outBuffer.insert(outBuffer.end(), si, si + result.scatterIndices.size() * sizeof(uint16_t)); + } + - + - // ------------------------------------------------------ + - // other modes in else branch + - // ------------------------------------------------------ + - else { + - // We'll fill these once a solution is found + + if (verbose) { + + std::cerr << "\r[Enc] Mode: parascatter, Block " << (blockIndex + 1) << "/" << totalBlocks << " "; + + } + + } else { + + // Other modes + bool found = false; + std::vector chosenNonce(nonceSize, 0); + std::vector scatterIndices(thisBlockSize, 0); + + for (uint64_t tries = 0; ; tries++) { + - // Generate the nonce + + // Generate nonce + if (deterministicNonce) { + for (size_t i = 0; i < nonceSize; i++) { + chosenNonce[i] = static_cast((nonceCounter >> (i * 8)) & 0xFF); + } + nonceCounter++; + - } + - else { + + } else { + for (size_t i = 0; i < nonceSize; i++) { + chosenNonce[i] = dist(rng); + } + } + + - // Build trial buffer + + // Build trial + std::vector trial(blockSubkey); + trial.insert(trial.end(), chosenNonce.begin(), chosenNonce.end()); + +@@@ -188,23 -222,42 +240,23 @@@ + std::vector hashOut(hash_size / 8); + invokeHash(algot, seed, trial, hashOut, hash_size); + + - // If outputExtension > 0, extend the output + - std::vector finalHashOut = hashOut; // Default to OG hashOut + + // Possibly extend output + + std::vector finalHashOut = hashOut; + if (outputExtension > 0) { + - // Derive PRK using trial, salt, and seed + - /* + - std::vector prk = derivePRK( + - trial, // Input key material: subkey || nonce + - salt, // Salt from encryption call + - keyBuf, // IKM from encryption call + - algot, // Hash algorithm + - hash_size // Base hash size + - ); + - */ + - + - // Extend the PRK to derive additional bytes (extendedOutput) + - size_t extendedSize = outputExtension; // Additional bytes required + - std::vector extendedOutput = extendOutputKDF( + - trial, // PRK derived from trial + - extendedSize, // Length of the extension + - algot, // Hash algorithm + - hash_size // Original hash size + - ); + - + - // Combine hashOut and extendedOutput + + // For demonstration, use the subkey+nonce as the "PRK" input + + std::vector extendedOutput = extendOutputKDF(trial, outputExtension, algot, hash_size); + finalHashOut.insert(finalHashOut.end(), extendedOutput.begin(), extendedOutput.end()); + } + + + // Check the search mode + if (searchModeEnum == 0x00) { // prefix + if (finalHashOut.size() >= thisBlockSize && + std::equal(block.begin(), block.end(), finalHashOut.begin())) { + scatterIndices.assign(thisBlockSize, 0); + found = true; + } + - } + - else if (searchModeEnum == 0x01) { // sequence + - for (size_t i = 0; i <= finalHashOut.size() - thisBlockSize; i++) { + + } else if (searchModeEnum == 0x01) { // sequence + + for (size_t i = 0; i + thisBlockSize <= finalHashOut.size(); i++) { + if (std::equal(block.begin(), block.end(), finalHashOut.begin() + i)) { + uint16_t startIdx = static_cast(i); + scatterIndices.assign(thisBlockSize, startIdx); +@@@ -212,9 -265,9 +264,9 @@@ + break; + } + } + - } + - else if (searchModeEnum == 0x02) { // series + + } else if (searchModeEnum == 0x02) { // series + bool allFound = true; + + std::bitset<256 * 256> usedIndices; + usedIndices.reset(); + auto it = finalHashOut.begin(); + +@@@ -229,7 -282,8 +281,7 @@@ + break; + } + ++it; + - } + - else { + + } else { + allFound = false; + break; + } +@@@ -239,12 -293,20 +291,12 @@@ + break; + } + } + - + if (allFound) { + found = true; + - if (verbose) { + - std::cout << "Series Indices: "; + - for (auto idx : scatterIndices) { + - std::cout << static_cast(idx) << " "; + } + - std::cout << std::endl; + - } + - } + - } + - else if (searchModeEnum == 0x03) { // scatter + + } else if (searchModeEnum == 0x03) { // scatter + bool allFound = true; + + std::bitset<256 * 256> usedIndices; + usedIndices.reset(); + + for (size_t byteIdx = 0; byteIdx < thisBlockSize; byteIdx++) { +@@@ -259,32 -321,41 +311,32 @@@ + break; + } + ++it; + - } + - else { + + } else { + allFound = false; + break; + } + } + - if (it == finalHashOut.end()) { + - allFound = false; + + if (!allFound) { + break; + } + } + - + if (allFound) { + found = true; + - if (verbose) { + - std::cout << "Scatter Indices: "; + - for (auto idx : scatterIndices) { + - std::cout << static_cast(idx) << " "; + - } + - std::cout << std::endl; + } + - } + - } + - else if (searchModeEnum == 0x04) { // mapscatter + - // Reset offsets + - std::fill(std::begin(reverseMapOffsets), std::end(reverseMapOffsets), 0); + + } else if (searchModeEnum == 0x04) { // mapscatter + + bool allFound = true; + + static uint8_t reverseMap[256 * 256]; + + static uint8_t reverseMapOffsets[256]; + + - // Fill the map with all positions of each byte in finalHashOut + + memset(reverseMapOffsets, 0, sizeof(reverseMapOffsets)); + + + + // Build map + for (uint16_t i = 0; i < finalHashOut.size(); i++) { + uint8_t b = finalHashOut[i]; + reverseMap[b * 256 + reverseMapOffsets[b]] = i; + reverseMapOffsets[b]++; + } + + - bool allFound = true; + for (size_t byteIdx = 0; byteIdx < thisBlockSize; ++byteIdx) { + uint8_t targetByte = block[byteIdx]; + if (reverseMapOffsets[targetByte] == 0) { +@@@ -295,325 -366,275 +347,325 @@@ + scatterIndices[byteIdx] = + reverseMap[targetByte * 256 + reverseMapOffsets[targetByte]]; + } + - + if (allFound) { + found = true; + - if (verbose) { + - std::cout << "Scatter Indices: "; + - for (auto idx : scatterIndices) { + - std::cout << static_cast(idx) << " "; + - } + - std::cout << std::endl; + - } + } + } + + if (found) { + - // For non-parallel modes, write nonce and indices + - fout.write(reinterpret_cast(chosenNonce.data()), nonceSize); + - if (searchModeEnum == 0x02 || searchModeEnum == 0x03 || searchModeEnum == 0x04) { + - fout.write(reinterpret_cast(scatterIndices.data()), scatterIndices.size() * sizeof(uint16_t)); + - + - } + - else { + - // prefix or sequence + + // Write nonce + + outBuffer.insert(outBuffer.end(), chosenNonce.begin(), chosenNonce.end()); + + // Write indices + + if (searchModeEnum == 0x02 || searchModeEnum == 0x03 || + + searchModeEnum == 0x04) { + + const uint8_t* si = reinterpret_cast(scatterIndices.data()); + + outBuffer.insert(outBuffer.end(), si, si + scatterIndices.size() * sizeof(uint16_t)); + + } else if (searchModeEnum == 0x00 || searchModeEnum == 0x01) { + uint16_t startIdx = scatterIndices[0]; + - fout.write(reinterpret_cast(&startIdx), sizeof(startIdx)); + + const uint8_t* idxPtr = reinterpret_cast(&startIdx); + + outBuffer.insert(outBuffer.end(), idxPtr, idxPtr + sizeof(startIdx)); + } + - break; // done with this block + + if (verbose) { + + std::cerr << "\r[Enc] Block " << blockIndex + 1 << "/" << totalBlocks << " found pattern.\n"; + + } + + break; + } + + - // Periodic progress for non-parallel tries + - if (tries % progressInterval == 0) { + + if (tries % progressInterval == 0 && verbose) { + std::cerr << "\r[Enc] Mode: " << searchMode + << ", Block " << (blockIndex + 1) << "/" << totalBlocks + << ", " << tries << " tries..." << std::flush; + } + - } // end tries loop + - + - // Display block progress + - if (verbose) { + - std::cerr << "\r[Enc] Block " << blockIndex + 1 << "/" << totalBlocks << " processed.\n"; + + } + + } + } + + - } // end else (other modes) + - + - } // end block loop + - + - fout.close(); + - std::cout << "\n[Enc] Block-based puzzle encryption with subkeys complete: " << outFilename << "\n"; + + // Return the fully built buffer + + return outBuffer; + } + + - // encrypt/decrypt block mode + - static void puzzleDecryptFileWithHeader( + - const std::string &inFilename, + - const std::string &outFilename, + +static std::vector puzzleDecryptBufferWithHeader( + + const std::vector &cipherData, + const std::string &key + ) { + - // 1) Open input file & read FileHeader + - std::ifstream fin(inFilename, std::ios::binary); + - if (!fin.is_open()) { + - throw std::runtime_error("Cannot open ciphertext file: " + inFilename); + - } + + // We'll parse the FileHeader from the front of cipherData + + // Then reconstruct the plaintext. + + - // Read unified FileHeader + - FileHeader hdr = readFileHeader(fin); + - if (hdr.magic != MagicNumber) { // "RCRY" + - throw std::runtime_error("Invalid magic number in file header."); + + if (cipherData.size() < sizeof(FileHeader) - 32) { + + throw std::runtime_error("Cipher data too small to contain valid header."); + } + + - // Determine cipher mode + - if (hdr.cipherMode != 0x11) { // 0x11 = Block Cipher Mode + - throw std::runtime_error("File is not in Block Cipher mode (expected cipherMode=0x11)."); + + size_t offset = 0; + + auto readAndAdvance = [&](void* dst, size_t len) { + + if (offset + len > cipherData.size()) { + + throw std::runtime_error("Buffer overrun reading cipher data."); + } + + std::memcpy(dst, &cipherData[offset], len); + + offset += len; + + }; + + - // Validate hash algorithm + + FileHeader hdr{}; + + // Magic + + readAndAdvance(&hdr.magic, sizeof(hdr.magic)); + + // Version + + readAndAdvance(&hdr.version, sizeof(hdr.version)); + + // cipherMode + + readAndAdvance(&hdr.cipherMode, sizeof(hdr.cipherMode)); + + // blockSize + + readAndAdvance(&hdr.blockSize, sizeof(hdr.blockSize)); + + // nonceSize + + readAndAdvance(&hdr.nonceSize, sizeof(hdr.nonceSize)); + + // hashSizeBits + + readAndAdvance(&hdr.hashSizeBits, sizeof(hdr.hashSizeBits)); + + // outputExtension + + readAndAdvance(&hdr.outputExtension, sizeof(hdr.outputExtension)); + + // hashName length + + uint8_t hnLen = 0; + + readAndAdvance(&hnLen, sizeof(hnLen)); + + hdr.hashName.resize(hnLen); + + if (hnLen > 0) { + + readAndAdvance(&hdr.hashName[0], hnLen); + + } + + // iv + + readAndAdvance(&hdr.iv, sizeof(hdr.iv)); + + // saltLen + + readAndAdvance(&hdr.saltLen, sizeof(hdr.saltLen)); + + hdr.salt.resize(hdr.saltLen); + + if (hdr.saltLen > 0) { + + readAndAdvance(hdr.salt.data(), hdr.saltLen); + + } + + // searchModeEnum + + readAndAdvance(&hdr.searchModeEnum, sizeof(hdr.searchModeEnum)); + + // originalSize + + readAndAdvance(&hdr.originalSize, sizeof(hdr.originalSize)); + + // hmac + + readAndAdvance(hdr.hmac.data(), hdr.hmac.size()); + + + + if (hdr.magic != MagicNumber) { + + throw std::runtime_error("Invalid magic number."); + + } + + if (hdr.cipherMode != 0x11) { + + throw std::runtime_error("Not block cipher mode (expected 0x11)."); + + } + + + + // Determine HashAlgorithm + HashAlgorithm algot = HashAlgorithm::Unknown; + if (hdr.hashName == "rainbow") { + algot = HashAlgorithm::Rainbow; + - } + - else if (hdr.hashName == "rainstorm") { + + } else if (hdr.hashName == "rainstorm") { + algot = HashAlgorithm::Rainstorm; + - } + - else { + - throw std::runtime_error("Unsupported hash algorithm in header: " + hdr.hashName); + + } else { + + throw std::runtime_error("Unsupported hash algorithm: " + hdr.hashName); + } + + - // Read the rest of the file as cipher data + - std::vector cipherData( + - (std::istreambuf_iterator(fin)), + - (std::istreambuf_iterator()) + - ); + - fin.close(); + - + - // 2) Derive PRK using KDF + - // Assume 'derivePRK' and 'extendOutputKDF' are defined in tool.h and in scope + + // Remainder is actual block data + + // Derive PRK + std::vector ikm(key.begin(), key.end()); + - // Convert seed (uint64_t) to vector + std::vector seed_vec(8); + - uint64_t seed = hdr.iv; + for (size_t i = 0; i < 8; ++i) { + - seed_vec[i] = static_cast((seed >> (i * 8)) & 0xFF); + + seed_vec[i] = static_cast((hdr.iv >> (i * 8)) & 0xFF); + } + + std::vector prk = derivePRK(seed_vec, hdr.salt, ikm, algot, hdr.hashSizeBits); + + - std::vector prk = derivePRK( + - seed_vec, // Converted seed as vector + - hdr.salt, // Provided salt + - ikm, // IKM (Input Key Material) + - algot, // Hash algorithm + - hdr.hashSizeBits // Hash size in bits + - ); + - + - // 3) Extend PRK into subkeys for each block + + // Extend into subkeys + size_t totalBlocks = (hdr.originalSize + hdr.blockSize - 1) / hdr.blockSize; + size_t subkeySize = hdr.hashSizeBits / 8; + size_t totalNeeded = totalBlocks * subkeySize; + - std::vector allSubkeys = extendOutputKDF( + - prk, + - totalNeeded, + - algot, + - hdr.hashSizeBits + - ); + + std::vector allSubkeys = extendOutputKDF(prk, totalNeeded, algot, hdr.hashSizeBits); + + - // 4) Prepare to reconstruct plaintext + + // Reconstruct + std::vector plaintextAccumulated; + plaintextAccumulated.reserve(hdr.originalSize); + + - size_t cipherOffset = 0; + - size_t recoveredSoFar = 0; + - + for (size_t blockIndex = 0; blockIndex < totalBlocks; blockIndex++) { + - size_t thisBlockSize = std::min(static_cast(hdr.blockSize), hdr.originalSize - recoveredSoFar); + - std::vector block; + + size_t thisBlockSize = std::min(hdr.blockSize, hdr.originalSize - plaintextAccumulated.size()); + + - // 4.1) Extract storedNonce + - if (cipherOffset + hdr.nonceSize > cipherData.size()) { + - throw std::runtime_error("Unexpected end of cipher data when reading nonce."); + + // Read storedNonce + + if (offset + hdr.nonceSize > cipherData.size()) { + + throw std::runtime_error("Cipher data ended while reading nonce."); + } + std::vector storedNonce( + - cipherData.begin() + cipherOffset, + - cipherData.begin() + cipherOffset + hdr.nonceSize + + cipherData.begin() + offset, + + cipherData.begin() + offset + hdr.nonceSize + ); + - cipherOffset += hdr.nonceSize; + + offset += hdr.nonceSize; + + - // 4.2) Read scatterIndices or startIndex based on searchModeEnum + + // Read scatterIndices or startIndex + std::vector scatterIndices; + uint16_t startIndex = 0; + if (hdr.searchModeEnum == 0x02 || hdr.searchModeEnum == 0x03 || + hdr.searchModeEnum == 0x04 || hdr.searchModeEnum == 0x05) { + - // Series, Scatter, MapScatter, Parascatter + size_t scatterDataSize = thisBlockSize * sizeof(uint16_t); + - if (cipherOffset + scatterDataSize > cipherData.size()) { + - throw std::runtime_error("Unexpected end of cipher data when reading scatter indices."); + + if (offset + scatterDataSize > cipherData.size()) { + + throw std::runtime_error("Cipher data ended while reading scatter indices."); + } + scatterIndices.assign( + - reinterpret_cast(&cipherData[cipherOffset]), + - reinterpret_cast(&cipherData[cipherOffset + scatterDataSize]) + + reinterpret_cast(&cipherData[offset]), + + reinterpret_cast(&cipherData[offset + scatterDataSize]) + ); + - cipherOffset += scatterDataSize; + - } + - else { + - // Prefix or Sequence + - if (cipherOffset + 1 > cipherData.size()) { + - throw std::runtime_error("Unexpected end of cipher data when reading start index."); + + offset += scatterDataSize; + + } else { + + // prefix or sequence + + if (offset + sizeof(uint16_t) > cipherData.size()) { + + throw std::runtime_error("Cipher data ended while reading start index."); + } + - startIndex = cipherData[cipherOffset]; + - cipherOffset += 1; + + // We wrote a uint16_t, but for prefix/sequence we might have only written 1 byte, + + // so adapt as needed if your real code only wrote 1 byte. We'll assume full 2 bytes. + + std::memcpy(&startIndex, &cipherData[offset], sizeof(startIndex)); + + offset += sizeof(startIndex); + } + + - // 4.3) Derive subkey for this block + + // Subkey + size_t subkeyOffset = blockIndex * subkeySize; + if (subkeyOffset + subkeySize > allSubkeys.size()) { + - throw std::runtime_error("Subkey index out of range."); + + throw std::runtime_error("Subkey index out of range in decryption."); + } + std::vector blockSubkey( + allSubkeys.begin() + subkeyOffset, + allSubkeys.begin() + subkeyOffset + subkeySize + ); + + - // 4.4) Recompute the hash using blockSubkey and storedNonce + + // Recompute hash + std::vector trial(blockSubkey); + trial.insert(trial.end(), storedNonce.begin(), storedNonce.end()); + + std::vector hashOut(hdr.hashSizeBits / 8); + - invokeHash(algot, hdr.iv, trial, hashOut, hdr.hashSizeBits); // Assuming 'hdr.iv' is passed correctly + + invokeHash(algot, hdr.iv, trial, hashOut, hdr.hashSizeBits); + + - // If outputExtension > 0, extend the output + - std::vector finalHashOut = hashOut; // Default to OG hashOut + + std::vector finalHashOut = hashOut; + if (hdr.outputExtension > 0) { + - // Derive PRK using trial, salt, and seed + - /* + - std::vector prk = derivePRK( + - trial, // Input key material: subkey || nonce + - hdr.salt, // Salt from file header + - ikm, // IKM derived from decryption call + - algot, // Hash algorithm + - hdr.hashSizeBits // Base hash size + - ); + - */ + - + - // Extend the PRK to derive additional bytes (extendedOutput) + - size_t extendedSize = hdr.outputExtension; // Additional bytes from header + - std::vector extendedOutput = extendOutputKDF( + - trial, // PRK derived from trial + - extendedSize, // Length of the extension + - algot, // Hash algorithm + - hdr.hashSizeBits // Original hash size + - ); + - + - // Combine hashOut and extendedOutput + - finalHashOut.insert(finalHashOut.end(), extendedOutput.begin(), extendedOutput.end()); + + std::vector extended = extendOutputKDF(trial, hdr.outputExtension, algot, hdr.hashSizeBits); + + finalHashOut.insert(finalHashOut.end(), extended.begin(), extended.end()); + } + + - // 4.5) Reconstruct plaintext block based on searchModeEnum + - if (hdr.searchModeEnum == 0x00) { // Prefix + - if (finalHashOut.size() < thisBlockSize/sizeof(uint16_t)) { + - throw std::runtime_error("[Dec] Hash output smaller than block size for Prefix mode."); + + // Reconstruct block + + std::vector block; + + block.reserve(thisBlockSize); + + + + if (hdr.searchModeEnum == 0x00) { // prefix + + if (finalHashOut.size() < thisBlockSize) { + + throw std::runtime_error("Hash output smaller than block size in prefix mode."); + } + block.assign(finalHashOut.begin(), finalHashOut.begin() + thisBlockSize); + + } else if (hdr.searchModeEnum == 0x01) { // sequence + + if (startIndex + thisBlockSize > finalHashOut.size()) { + + throw std::runtime_error("Start index out of bounds in sequence mode."); + } + - else if (hdr.searchModeEnum == 0x01) { // Sequence + - if (startIndex + thisBlockSize/sizeof(uint16_t) > finalHashOut.size()) { + - throw std::runtime_error("[Dec] Start index out of bounds in Sequence mode."); + - } + - block.assign(finalHashOut.begin() + startIndex, finalHashOut.begin() + startIndex + thisBlockSize); + - } + - else if (hdr.searchModeEnum == 0x02 || hdr.searchModeEnum == 0x03 || + - hdr.searchModeEnum == 0x04 || hdr.searchModeEnum == 0x05) { // Series, Scatter, MapScatter, Parascatter + - block.reserve(thisBlockSize); + + block.assign(finalHashOut.begin() + startIndex, + + finalHashOut.begin() + startIndex + thisBlockSize); + + } else if (hdr.searchModeEnum == 0x02 || hdr.searchModeEnum == 0x03 || + + hdr.searchModeEnum == 0x04 || hdr.searchModeEnum == 0x05) { + for (size_t j = 0; j < thisBlockSize; j++) { + uint16_t idx = scatterIndices[j]; + if (idx >= finalHashOut.size()) { + - // std::cerr << "idx " << idx << " final hash out size " << finalHashOut.size() << std::flush; + - throw std::runtime_error("[Dec] Scatter index out of range."); + + throw std::runtime_error("Scatter index out of range in finalHashOut."); + } + block.push_back(finalHashOut[idx]); + } + - } + - else { + - throw std::runtime_error("Invalid search mode enum in decryption."); + + } else { + + throw std::runtime_error("Invalid searchModeEnum in decryption."); + } + + - // 4.6) Accumulate the reconstructed block + - plaintextAccumulated.insert( + - plaintextAccumulated.end(), + - block.begin(), + - block.end() + - ); + - recoveredSoFar += thisBlockSize; + + plaintextAccumulated.insert(plaintextAccumulated.end(), block.begin(), block.end()); + + - // 4.7) Progress Reporting + if (blockIndex % 100 == 0) { + - std::cerr << "\r[Dec] Processing block " << (blockIndex + 1) << " / " << totalBlocks << "..." << std::flush; + + std::cerr << "\r[Dec] Processing block " << (blockIndex + 1) << "/" << totalBlocks << "..."; + + } + } + - } // end for(blockIndex) + - + - std::cout << "\n[Dec] Ciphertext blocks decrypted successfully.\n"; + + - // 5) Decompress the accumulated plaintext + - std::vector decompressedData = decompressData(plaintextAccumulated); + + // Done reading, now decompress + + //std::vector decompressedData = decompressData(plaintextAccumulated); + + std::vector decompressedData = plaintextAccumulated; + if (plaintextAccumulated.size() != hdr.originalSize) { + - throw std::runtime_error("Compressed data size does not match original size."); + + throw std::runtime_error("Compressed data size mismatch vs. original size header."); + + } + + + + return decompressedData; + } + + - // 6) Write the decompressed plaintext to output file + +static void puzzleEncryptFileWithHeader( + + const std::string &inFilename, + + const std::string &outFilename, + + const std::string &key, + + HashAlgorithm algot, + + uint32_t hash_size, + + uint64_t seed, + + std::vector salt, + + size_t blockSize, + + size_t nonceSize, + + const std::string &searchMode, + + bool verbose, + + bool deterministicNonce, + + uint32_t outputExtension + +) { + + // 1) Read & compress plaintext from file + + std::ifstream fin(inFilename, std::ios::binary); + + if (!fin.is_open()) { + + throw std::runtime_error("Cannot open input file: " + inFilename); + + } + + std::vector plainData( + + (std::istreambuf_iterator(fin)), + + (std::istreambuf_iterator()) + + ); + + fin.close(); + + + + // 2) Call the new buffer-based API + + std::vector encrypted = puzzleEncryptBufferWithHeader( + + plainData, + + key, + + algot, + + hash_size, + + seed, + + salt, + + blockSize, + + nonceSize, + + searchMode, + + verbose, + + deterministicNonce, + + outputExtension + + ); + + + + // 3) Write the resulting ciphertext to file + std::ofstream fout(outFilename, std::ios::binary); + if (!fout.is_open()) { + - throw std::runtime_error("Cannot open output file for decompressed plaintext: " + outFilename); + + throw std::runtime_error("Cannot open output file: " + outFilename); + } + + fout.write(reinterpret_cast(encrypted.data()), encrypted.size()); + + fout.close(); + + + std::cout << "\n[Enc] Block-based puzzle encryption with subkeys complete: " << outFilename << "\n"; + +} + + + +static void puzzleDecryptFileWithHeader( + + const std::string &inFilename, + + const std::string &outFilename, + + const std::string &key + +) { + + // 1) Read the ciphertext file + + std::ifstream fin(inFilename, std::ios::binary); + + if (!fin.is_open()) { + + throw std::runtime_error("Cannot open ciphertext file: " + inFilename); + + } + + std::vector cipherData( + + (std::istreambuf_iterator(fin)), + + (std::istreambuf_iterator()) + + ); + + fin.close(); + + + + // 2) Call the new buffer-based API + + std::vector decompressedData = puzzleDecryptBufferWithHeader( + + cipherData, + + key + + ); + + + + // 3) Write the decompressed plaintext to file + + std::ofstream fout(outFilename, std::ios::binary); + + if (!fout.is_open()) { + + throw std::runtime_error("Cannot open output file for plaintext: " + outFilename); + + } + fout.write(reinterpret_cast(decompressedData.data()), decompressedData.size()); + fout.close(); + + std::cout << "[Dec] Decompressed plaintext written to: " << outFilename << "\n"; + } + + diff --git a/js/lib/api.mjs b/js/lib/api.mjs index 0aa997b..a270487 100644 --- a/js/lib/api.mjs +++ b/js/lib/api.mjs @@ -271,8 +271,11 @@ export async function streamDecryptBuffer( throw new Error(errorMessage || "Decryption failed or returned empty buffer."); } - // Copy decrypted data from WASM memory to Node.js Buffer - const decryptedData = Buffer.from(HEAPU8.buffer, decryptedPtr, decryptedSize); + // Safer approach: copy from subarray + const decryptedCopy = new Uint8Array( + rain.HEAPU8.subarray(decryptedPtr, decryptedPtr + decryptedSize) + ); + const decryptedData = Buffer.from(decryptedCopy); // Free allocated memory in WASM wasmFreeBuffer(decryptedPtr); @@ -407,6 +410,88 @@ export async function streamEncryptBuffer( } } +// Just the snippet showing the new block-based API wrappers in api.mjs: + +/** + * Encrypts a buffer using the WASM block-based encryption function. + */ +export async function blockEncryptBuffer( + plainData, + password +) { + if (!rain.loaded) { + await loadRain(); + } + return rain.encryptData(plainData, password); +} + +/** + * Decrypts a buffer using the WASM block-based decryption function. + */ +export async function blockDecryptBuffer( + cipherData, + password +) { + if (!rain.loaded) { + await loadRain(); + } + + const { + wasmExports: { wasmBlockDecryptBuffer, wasmFreeBuffer, malloc: _malloc, free: _free }, + HEAPU8, + HEAPU32 + } = rain; + + // Allocate memory for ciphertext + const cipherPtr = _malloc(cipherData.length); + HEAPU8.set(cipherData, cipherPtr); + + // Allocate memory for password + const passwordLength = Buffer.byteLength(password, 'utf-8'); + const passwordPtr = _malloc(passwordLength + 1); + HEAPU8.set(Buffer.from(password, 'utf-8'), passwordPtr); + HEAPU8[passwordPtr + passwordLength] = 0; + + // Allocate output pointer + const outBufferPtr = _malloc(4); // 32-bit + const outBufferSizePtr = _malloc(4); + + try { + // Call the WASM function + wasmBlockDecryptBuffer( + cipherPtr, + cipherData.length, + passwordPtr, + passwordLength, + outBufferPtr, + outBufferSizePtr + ); + + // Grab the result + const decryptedPtr = HEAPU32[outBufferPtr >> 2]; + const decryptedSize = HEAPU32[outBufferSizePtr >> 2]; + + if (!decryptedPtr || !decryptedSize) { + throw new Error("Block decryption returned empty buffer."); + } + + // Copy out the result + const decryptedData = Buffer.from(HEAPU8.buffer, decryptedPtr, decryptedSize); + + // Free the result buffer + wasmFreeBuffer(decryptedPtr); + + return decryptedData; + } finally { + // Clean up + _free(cipherPtr); + _free(passwordPtr); + _free(outBufferPtr); + _free(outBufferSizePtr); + } +} + + /** * Loads the WASM module and wraps the necessary functions. */ @@ -448,11 +533,46 @@ async function loadRain() { 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]); + + // Inside loadRain(), once the wasm is loaded: + rain.wasmBlockEncryptBuffer = cwrap('wasmBlockEncryptBuffer', 'number', [ + 'number', 'number', 'string', 'number' + ]); + rain.wasmBlockDecryptBuffer = cwrap('wasmBlockDecryptBuffer', null, [ + 'number','number','number','number','number','number' + ]); + rain.wasmFreeBuffer = cwrap('wasmFreeBuffer', null, ['number']); // Wrap the new wasmFreeString function rain.wasmFreeString = cwrap('wasmFreeString', null, ['number']); + const toUint8Array = (data) => { + const buffer = new Uint8Array(data.length); + for (let i = 0; i < data.length; i++) buffer[i] = data[i]; + return buffer; + }; + + const fromUint8Array = (buffer) => String.fromCharCode.apply(null, buffer); + + const encryptData = (plainText, key) => { + const dataPtr = rain._malloc(plainText.length); + rain.HEAPU8.set(plainText, dataPtr); + + const outLenPtr = rain._malloc(4); // Allocate space for size_t (4 bytes) + const resultPtr = rain.wasmBlockEncryptBuffer(dataPtr, plainText.length, key, outLenPtr); + + const resultLen = rain.HEAPU32[outLenPtr >> 2]; // Read size_t value + const encrypted = Buffer.from(rain.HEAPU8.buffer, resultPtr, resultLen) + + rain._free(dataPtr); + rain._free(outLenPtr); + + return encrypted; + }; + + rain.encryptData = encryptData; + resolve(); }).catch(err => { console.error('Failed to load WASM module:', err); diff --git a/js/rainsum.mjs b/js/rainsum.mjs index 98263e7..367a2b3 100755 --- a/js/rainsum.mjs +++ b/js/rainsum.mjs @@ -3,12 +3,14 @@ import fs from 'fs'; import process from 'process'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; -import { - rainbowHash, - rainstormHash, +import { + rainbowHash, + rainstormHash, getFileHeaderInfo, - streamEncryptBuffer, + streamEncryptBuffer, streamDecryptBuffer, + blockEncryptBuffer, // NEW + blockDecryptBuffer, // NEW rain } from './lib/api.mjs'; @@ -37,9 +39,9 @@ const argv = yargs(hideBin(process.argv)) .usage('Usage: $0 [options] [file]') .option('mode', { alias: 'm', - description: 'Mode: digest, stream, info, stream-enc, or dec', + description: 'Mode: digest, stream, info, stream-enc, block-enc, or dec', type: 'string', - choices: ['digest', 'stream', 'info', 'stream-enc', 'dec'], + choices: ['digest', 'stream', 'info', 'stream-enc', 'block-enc', 'dec'], default: 'digest', }) .option('algorithm', { @@ -210,25 +212,78 @@ async function handleMode(mode, algorithm, seed, inputPath, outputPath, size) { console.log(`[stream-enc] Encrypted data written to: ${outputPath}`); } } - else if (mode === 'dec') { - // Handle decryption + else if (mode === 'block-enc') { + // (Or you can add more CLI flags for blockSize, nonceSize, searchMode, etc.) const password = argv.password || ''; const verbose = argv.verbose; + + // For demonstration, let's just pick some sample defaults + const blockSize = 6; + const nonceSize = 8; + const outputExtension = 128; + const searchMode = 'scatter'; // or 'prefix', 'sequence', etc. + const deterministicNonce = false; - // Validate password + // Validate password and salt if (!password) { - throw new Error("Password is required for decryption."); + throw new Error("Password is required for stream encryption."); } - // Call the buffer-based decryption function - const decryptedBuffer = await streamDecryptBuffer( - buffer, // Encrypted data buffer - password, // Decryption key - verbose // Verbose flag + // Call the new blockEncryptBuffer + const encryptedBuffer = await blockEncryptBuffer( + buffer, // Plain data + password, // Password ); + fs.writeFileSync(outputPath, encryptedBuffer); + if (verbose) { + console.log(`[block-enc] Block-based encryption written to: ${outputPath}`); + } + } + else if (mode === 'dec') { + const password = argv.password || ''; + const verbose = argv.verbose; + if (!password) { + throw new Error("Password is required for decryption."); + } + + // 1) Check the file header + const headerInfoStr = await getFileHeaderInfo(buffer); + let isBlockMode = false; + + try { + const headerJson = JSON.parse(headerInfoStr); + // If cipherMode is "0x11", it’s block-mode puzzle + if (headerJson.cipherMode === '0x11') { + isBlockMode = true; + } + } catch(e) { + // If it fails, we’ll assume it’s stream-based + } + + let decryptedBuffer; + if (isBlockMode) { + // 2) Decrypt block-based + decryptedBuffer = await blockDecryptBuffer( + buffer, + password + ); + if (verbose) { + console.log(`[dec] Detected block cipher mode, using block decryption.`); + } + } else { + // 2) Decrypt stream-based + decryptedBuffer = await streamDecryptBuffer( + buffer, + password, + verbose + ); + if (verbose) { + console.log(`[dec] No block cipher header found, using stream decryption.`); + } + } - // Write decrypted data to output file + // 3) Write decrypted result fs.writeFileSync(outputPath, decryptedBuffer); if (verbose) { console.log(`[dec] Decrypted data written to: ${outputPath}`); @@ -240,8 +295,6 @@ async function handleMode(mode, algorithm, seed, inputPath, outputPath, size) { await hashBuffer(mode, algorithm, seed, buffer, outputStream, size, inputName); } } catch (e) { - const errAddress = rain.HEAPU8[e] - console.log(rain.UTF8ToString(errAddress)); console.error('Error:', e); console.error(e.stack); process.exit(1); diff --git a/js/wasm/rain.cjs b/js/wasm/rain.cjs index acb9402..1383fce 100644 --- a/js/wasm/rain.cjs +++ b/js/wasm/rain.cjs @@ -63,6 +63,15 @@ function locateFile(path) { var readAsync, readBinary; if (ENVIRONMENT_IS_NODE) { + if (typeof process == 'undefined' || !process.release || process.release.name !== 'node') throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + + var nodeVersion = process.versions.node; + var numericVersion = nodeVersion.split('.').slice(0, 3); + numericVersion = (numericVersion[0] * 10000) + (numericVersion[1] * 100) + (numericVersion[2].split('-')[0] * 1); + var minVersion = 160000; + if (numericVersion < 160000) { + throw new Error('This emscripten-generated code requires node v16.0.0 (detected v' + nodeVersion + ')'); + } // These modules will usually be used on Node.js. Load them eagerly to avoid // the complexity of lazy-loading. @@ -76,6 +85,7 @@ readBinary = (filename) => { // We need to re-wrap `file://` strings to URLs. filename = isFileURI(filename) ? new URL(filename) : filename; var ret = fs.readFileSync(filename); + assert(Buffer.isBuffer(ret)); return ret; }; @@ -83,6 +93,7 @@ readAsync = async (filename, binary = true) => { // See the comment in the `readBinary` function. filename = isFileURI(filename) ? new URL(filename) : filename; var ret = fs.readFileSync(filename, binary ? undefined : 'utf8'); + assert(binary ? Buffer.isBuffer(ret) : typeof ret == 'string'); return ret; }; // end include: node_shell_read.js @@ -101,6 +112,11 @@ readAsync = async (filename, binary = true) => { throw toThrow; }; +} else +if (ENVIRONMENT_IS_SHELL) { + + if ((typeof process == 'object' && typeof require === 'function') || typeof window == 'object' || typeof WorkerGlobalScope != 'undefined') throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + } else // Note that this includes Node.js workers when relevant (pthreads is enabled). @@ -124,6 +140,8 @@ if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, '').lastIndexOf('/')+1); } + if (!(typeof window == 'object' || typeof WorkerGlobalScope != 'undefined')) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + { // include: web_or_worker_shell_read.js if (ENVIRONMENT_IS_WORKER) { @@ -167,6 +185,7 @@ if (ENVIRONMENT_IS_WORKER) { } } else { + throw new Error('environment detection error'); } var out = Module['print'] || console.log.bind(console); @@ -177,17 +196,44 @@ Object.assign(Module, moduleOverrides); // Free the object hierarchy contained in the overrides, this lets the GC // reclaim data used. moduleOverrides = null; +checkIncomingModuleAPI(); // Emit code to handle expected values on the Module object. This applies Module.x // to the proper local x. This has two benefits: first, we only emit it if it is // expected to arrive, and second, by using a local everywhere else that can be // minified. -if (Module['arguments']) arguments_ = Module['arguments']; +if (Module['arguments']) arguments_ = Module['arguments'];legacyModuleProp('arguments', 'arguments_'); -if (Module['thisProgram']) thisProgram = Module['thisProgram']; +if (Module['thisProgram']) thisProgram = Module['thisProgram'];legacyModuleProp('thisProgram', 'thisProgram'); // perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message +// Assertions on removed incoming Module JS APIs. +assert(typeof Module['memoryInitializerPrefixURL'] == 'undefined', 'Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['pthreadMainPrefixURL'] == 'undefined', 'Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['cdInitializerPrefixURL'] == 'undefined', 'Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['filePackagePrefixURL'] == 'undefined', 'Module.filePackagePrefixURL option was removed, use Module.locateFile instead'); +assert(typeof Module['read'] == 'undefined', 'Module.read option was removed'); +assert(typeof Module['readAsync'] == 'undefined', 'Module.readAsync option was removed (modify readAsync in JS)'); +assert(typeof Module['readBinary'] == 'undefined', 'Module.readBinary option was removed (modify readBinary in JS)'); +assert(typeof Module['setWindowTitle'] == 'undefined', 'Module.setWindowTitle option was removed (modify emscripten_set_window_title in JS)'); +assert(typeof Module['TOTAL_MEMORY'] == 'undefined', 'Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY'); +legacyModuleProp('asm', 'wasmExports'); +legacyModuleProp('readAsync', 'readAsync'); +legacyModuleProp('readBinary', 'readBinary'); +legacyModuleProp('setWindowTitle', 'setWindowTitle'); +var IDBFS = 'IDBFS is no longer included by default; build with -lidbfs.js'; +var PROXYFS = 'PROXYFS is no longer included by default; build with -lproxyfs.js'; +var WORKERFS = 'WORKERFS is no longer included by default; build with -lworkerfs.js'; +var FETCHFS = 'FETCHFS is no longer included by default; build with -lfetchfs.js'; +var ICASEFS = 'ICASEFS is no longer included by default; build with -licasefs.js'; +var JSFILEFS = 'JSFILEFS is no longer included by default; build with -ljsfilefs.js'; +var OPFS = 'OPFS is no longer included by default; build with -lopfs.js'; + +var NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js'; + +assert(!ENVIRONMENT_IS_SHELL, 'shell environment detected but not enabled at build time. Add `shell` to `-sENVIRONMENT` to enable.'); + // end include: shell.js // include: preamble.js @@ -201,7 +247,11 @@ if (Module['thisProgram']) thisProgram = Module['thisProgram']; // An online HTML version (which may be of a different version of Emscripten) // is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html -var wasmBinary = Module['wasmBinary']; +var wasmBinary = Module['wasmBinary'];legacyModuleProp('wasmBinary', 'wasmBinary'); + +if (typeof WebAssembly != 'object') { + err('no native wasm support detected'); +} // Wasm globals @@ -227,13 +277,13 @@ var EXITSTATUS; /** @type {function(*, string=)} */ function assert(condition, text) { if (!condition) { - // This build was created without ASSERTIONS defined. `assert()` should not - // ever be called in this configuration but in case there are callers in - // the wild leave this simple abort() implementation here for now. - abort(text); + abort('Assertion failed' + (text ? ': ' + text : '')); } } +// We used to include malloc/free by default in the past. Show a helpful error in +// builds with assertions. + // Memory management var HEAP, @@ -276,7 +326,52 @@ function updateMemoryViews() { } // end include: runtime_shared.js +assert(!Module['STACK_SIZE'], 'STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time') + +assert(typeof Int32Array != 'undefined' && typeof Float64Array !== 'undefined' && Int32Array.prototype.subarray != undefined && Int32Array.prototype.set != undefined, + 'JS engine does not provide full typed array support'); + +// If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY +assert(!Module['wasmMemory'], 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); +assert(!Module['INITIAL_MEMORY'], 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); + // include: runtime_stack_check.js +// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. +function writeStackCookie() { + var max = _emscripten_stack_get_end(); + assert((max & 3) == 0); + // If the stack ends at address zero we write our cookies 4 bytes into the + // stack. This prevents interference with SAFE_HEAP and ASAN which also + // monitor writes to address zero. + if (max == 0) { + max += 4; + } + // The stack grow downwards towards _emscripten_stack_get_end. + // We write cookies to the final two words in the stack and detect if they are + // ever overwritten. + HEAPU32[((max)>>2)] = 0x02135467; + HEAPU32[(((max)+(4))>>2)] = 0x89BACDFE; + // Also test the global address 0 for integrity. + HEAPU32[((0)>>2)] = 1668509029; +} + +function checkStackCookie() { + if (ABORT) return; + var max = _emscripten_stack_get_end(); + // See writeStackCookie(). + if (max == 0) { + max += 4; + } + var cookie1 = HEAPU32[((max)>>2)]; + var cookie2 = HEAPU32[(((max)+(4))>>2)]; + if (cookie1 != 0x02135467 || cookie2 != 0x89BACDFE) { + abort(`Stack overflow! Stack cookie has been overwritten at ${ptrToString(max)}, expected hex dwords 0x89BACDFE and 0x2135467, but received ${ptrToString(cookie2)} ${ptrToString(cookie1)}`); + } + // Also test the global address 0 for integrity. + if (HEAPU32[((0)>>2)] != 0x63736d65 /* 'emsc' */) { + abort('Runtime error: The application has corrupted its heap memory area (address zero)!'); + } +} // end include: runtime_stack_check.js var __ATPRERUN__ = []; // functions called before the runtime is initialized var __ATINIT__ = []; // functions called during startup @@ -296,8 +391,11 @@ function preRun() { } function initRuntime() { + assert(!runtimeInitialized); runtimeInitialized = true; + checkStackCookie(); + if (!Module['noFSInit'] && !FS.initialized) FS.init(); @@ -308,6 +406,7 @@ TTY.init(); } function postRun() { + checkStackCookie(); if (Module['postRun']) { if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; @@ -343,6 +442,10 @@ function addOnPostRun(cb) { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc +assert(Math.imul, 'This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.fround, 'This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.clz32, 'This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); +assert(Math.trunc, 'This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); // end include: runtime_math.js // A counter of dependencies for calling run(). If we need to // do asynchronous work before running, increment this and @@ -353,9 +456,15 @@ function addOnPostRun(cb) { // the dependencies are met. var runDependencies = 0; var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled +var runDependencyTracking = {}; +var runDependencyWatcher = null; function getUniqueRunDependency(id) { - return id; + var orig = id; + while (1) { + if (!runDependencyTracking[id]) return id; + id = orig + Math.random(); + } } function addRunDependency(id) { @@ -363,6 +472,33 @@ function addRunDependency(id) { Module['monitorRunDependencies']?.(runDependencies); + if (id) { + assert(!runDependencyTracking[id]); + runDependencyTracking[id] = 1; + if (runDependencyWatcher === null && typeof setInterval != 'undefined') { + // Check for missing dependencies every few seconds + runDependencyWatcher = setInterval(() => { + if (ABORT) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + return; + } + var shown = false; + for (var dep in runDependencyTracking) { + if (!shown) { + shown = true; + err('still waiting on run dependencies:'); + } + err(`dependency: ${dep}`); + } + if (shown) { + err('(end of list)'); + } + }, 10000); + } + } else { + err('warning: run dependency added without ID'); + } } function removeRunDependency(id) { @@ -370,7 +506,17 @@ function removeRunDependency(id) { Module['monitorRunDependencies']?.(runDependencies); + if (id) { + assert(runDependencyTracking[id]); + delete runDependencyTracking[id]; + } else { + err('warning: run dependency removed without ID'); + } if (runDependencies == 0) { + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } if (dependenciesFulfilled) { var callback = dependenciesFulfilled; dependenciesFulfilled = null; @@ -390,8 +536,6 @@ function abort(what) { ABORT = true; - what += '. Build with -sASSERTIONS for more info.'; - // Use a wasm runtime error, because a JS error might be seen as a foreign // exception, which means we'd run destructors on it. We need the error to // simply make the program stop. @@ -432,6 +576,17 @@ var isDataURI = (filename) => filename.startsWith(dataURIPrefix); */ var isFileURI = (filename) => filename.startsWith('file://'); // end include: URIUtils.js +function createExportWrapper(name, nargs) { + return (...args) => { + assert(runtimeInitialized, `native function \`${name}\` called before runtime initialization`); + var f = wasmExports[name]; + assert(f, `exported native function \`${name}\` not found`); + // Only assert for too many arguments. Too few can be valid since the missing arguments will be zero filled. + assert(args.length <= nargs, `native function \`${name}\` called with ${args.length} args but expects ${nargs}`); + return f(...args); + }; +} + // include: runtime_exceptions.js // end include: runtime_exceptions.js function findWasmBinary() { @@ -479,6 +634,10 @@ async function instantiateArrayBuffer(binaryFile, imports) { } catch (reason) { err(`failed to asynchronously prepare wasm: ${reason}`); + // Warn on some common problems. + if (isFileURI(wasmBinaryFile)) { + err(`warning: Loading from a file URI (${wasmBinaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`); + } abort(reason); } } @@ -534,6 +693,7 @@ async function createWasm() { wasmMemory = wasmExports['memory']; + assert(wasmMemory, 'memory not found in wasm exports'); updateMemoryViews(); addOnInit(wasmExports['__wasm_call_ctors']); @@ -545,9 +705,15 @@ async function createWasm() { addRunDependency('wasm-instantiate'); // Prefer streaming instantiation if available. + // Async compilation can be confusing when an error on the page overwrites Module + // (for example, if the order of elements is wrong, and the one defining Module is + // later), so we save Module and check it later. + var trueModule = Module; function receiveInstantiationResult(result) { // 'result' is a ResultObject object which has both the module and instance. // receiveInstance() will swap in the exports (to Module.asm) so they can be called + assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'); + trueModule = null; // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. // When the regression is fixed, can restore the above PTHREADS-enabled path. receiveInstance(result['instance']); @@ -578,6 +744,121 @@ async function createWasm() { } // include: runtime_debug.js +// Endianness check +(() => { + var h16 = new Int16Array(1); + var h8 = new Int8Array(h16.buffer); + h16[0] = 0x6373; + if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)'; +})(); + +if (Module['ENVIRONMENT']) { + throw new Error('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'); +} + +function legacyModuleProp(prop, newName, incoming=true) { + if (!Object.getOwnPropertyDescriptor(Module, prop)) { + Object.defineProperty(Module, prop, { + configurable: true, + get() { + let extra = incoming ? ' (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)' : ''; + abort(`\`Module.${prop}\` has been replaced by \`${newName}\`` + extra); + + } + }); + } +} + +function ignoredModuleProp(prop) { + if (Object.getOwnPropertyDescriptor(Module, prop)) { + abort(`\`Module.${prop}\` was supplied but \`${prop}\` not included in INCOMING_MODULE_JS_API`); + } +} + +// forcing the filesystem exports a few things by default +function isExportedByForceFilesystem(name) { + return name === 'FS_createPath' || + name === 'FS_createDataFile' || + name === 'FS_createPreloadedFile' || + name === 'FS_unlink' || + name === 'addRunDependency' || + // The old FS has some functionality that WasmFS lacks. + name === 'FS_createLazyFile' || + name === 'FS_createDevice' || + name === 'removeRunDependency'; +} + +/** + * Intercept access to a global symbol. This enables us to give informative + * warnings/errors when folks attempt to use symbols they did not include in + * their build, or no symbols that no longer exist. + */ +function hookGlobalSymbolAccess(sym, func) { + if (typeof globalThis != 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) { + Object.defineProperty(globalThis, sym, { + configurable: true, + get() { + func(); + return undefined; + } + }); + } +} + +function missingGlobal(sym, msg) { + hookGlobalSymbolAccess(sym, () => { + warnOnce(`\`${sym}\` is not longer defined by emscripten. ${msg}`); + }); +} + +missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); +missingGlobal('asm', 'Please use wasmExports instead'); + +function missingLibrarySymbol(sym) { + hookGlobalSymbolAccess(sym, () => { + // Can't `abort()` here because it would break code that does runtime + // checks. e.g. `if (typeof SDL === 'undefined')`. + var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`; + // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in + // library.js, which means $name for a JS name with no prefix, or name + // for a JS name like _name. + var librarySymbol = sym; + if (!librarySymbol.startsWith('_')) { + librarySymbol = '$' + sym; + } + msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + warnOnce(msg); + }); + + // Any symbol that is not included from the JS library is also (by definition) + // not exported on the Module object. + unexportedRuntimeSymbol(sym); +} + +function unexportedRuntimeSymbol(sym) { + if (!Object.getOwnPropertyDescriptor(Module, sym)) { + Object.defineProperty(Module, sym, { + configurable: true, + get() { + var msg = `'${sym}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + abort(msg); + } + }); + } +} + +// Used by XXXXX_DEBUG settings to output debug messages. +function dbg(...args) { + // TODO(sbc): Make this configurable somehow. Its not always convenient for + // logging to show up as warnings. + console.warn(...args); +} // end include: runtime_debug.js // === Body === // end include: preamble.js @@ -620,6 +901,13 @@ async function createWasm() { var noExitRuntime = Module['noExitRuntime'] || true; + var ptrToString = (ptr) => { + assert(typeof ptr === 'number'); + // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. + ptr >>>= 0; + return '0x' + ptr.toString(16).padStart(8, '0'); + }; + /** * @param {number} ptr @@ -645,6 +933,92 @@ async function createWasm() { var stackSave = () => _emscripten_stack_get_current(); + var warnOnce = (text) => { + warnOnce.shown ||= {}; + if (!warnOnce.shown[text]) { + warnOnce.shown[text] = 1; + if (ENVIRONMENT_IS_NODE) text = 'warning: ' + text; + err(text); + } + }; + + var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder() : undefined; + + /** + * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given + * array that contains uint8 values, returns a copy of that string as a + * Javascript String object. + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number=} idx + * @param {number=} maxBytesToRead + * @return {string} + */ + var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => { + var endIdx = idx + maxBytesToRead; + var endPtr = idx; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. Also, use the length info to avoid running tiny + // strings through TextDecoder, since .subarray() allocates garbage. + // (As a tiny code save trick, compare endPtr against endIdx using a negation, + // so that undefined/NaN means Infinity) + while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; + + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ''; + // If building with TextDecoder, we have already computed the string length + // above, so test loop end condition against that + while (idx < endPtr) { + // For UTF8 byte structure, see: + // http://en.wikipedia.org/wiki/UTF-8#Description + // https://www.ietf.org/rfc/rfc2279.txt + // https://tools.ietf.org/html/rfc3629 + var u0 = heapOrArray[idx++]; + if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } + var u1 = heapOrArray[idx++] & 63; + if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } + var u2 = heapOrArray[idx++] & 63; + if ((u0 & 0xF0) == 0xE0) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte ' + ptrToString(u0) + ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!'); + u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); + } + + if (u0 < 0x10000) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 0x10000; + str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); + } + } + return str; + }; + + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using one + * @return {string} + */ + var UTF8ToString = (ptr, maxBytesToRead) => { + assert(typeof ptr == 'number', `UTF8ToString expects a number (got ${typeof ptr})`); + return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; + }; + Module['UTF8ToString'] = UTF8ToString; + var ___assert_fail = (condition, filename, line, func) => + abort(`Assertion failed: ${UTF8ToString(condition)}, at: ` + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']); + class ExceptionInfo { // excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it. constructor(excPtr) { @@ -711,13 +1085,14 @@ async function createWasm() { info.init(type, destructor); exceptionLast = ptr; uncaughtExceptionCount++; - throw exceptionLast; + assert(false, 'Exception thrown, but exception catching is not enabled. Compile with -sNO_DISABLE_EXCEPTION_CATCHING or -sEXCEPTION_CATCHING_ALLOWED=[..] to catch.'); }; var __abort_js = () => - abort(''); + abort('native code called abort()'); var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { + assert(typeof str === 'string', `stringToUTF8Array expects a string (got ${typeof str})`); // Parameter maxBytesToWrite is not optional. Negative values, 0, null, // undefined and false each don't write out any bytes. if (!(maxBytesToWrite > 0)) @@ -752,6 +1127,7 @@ async function createWasm() { heap[outIdx++] = 0x80 | (u & 63); } else { if (outIdx + 3 >= endIdx) break; + if (u > 0x10FFFF) warnOnce('Invalid Unicode code point ' + ptrToString(u) + ' encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF).'); heap[outIdx++] = 0xF0 | (u >> 18); heap[outIdx++] = 0x80 | ((u >> 12) & 63); heap[outIdx++] = 0x80 | ((u >> 6) & 63); @@ -763,9 +1139,32 @@ async function createWasm() { return outIdx - startIdx; }; var stringToUTF8 = (str, outPtr, maxBytesToWrite) => { + assert(typeof maxBytesToWrite == 'number', 'stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!'); return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); }; Module['stringToUTF8'] = stringToUTF8; + + var lengthBytesUTF8 = (str) => { + var len = 0; + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code + // unit, not a Unicode code point of the character! So decode + // UTF16->UTF32->UTF8. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + var c = str.charCodeAt(i); // possibly a lead surrogate + if (c <= 0x7F) { + len++; + } else if (c <= 0x7FF) { + len += 2; + } else if (c >= 0xD800 && c <= 0xDFFF) { + len += 4; ++i; + } else { + len += 3; + } + } + return len; + }; + Module['lengthBytesUTF8'] = lengthBytesUTF8; var __tzset_js = (timezone, daylight, std_name, dst_name) => { // TODO: Use (malleable) environment variables instead of system settings. var currentYear = new Date().getFullYear(); @@ -805,6 +1204,10 @@ async function createWasm() { var winterName = extractZone(winterOffset); var summerName = extractZone(summerOffset); + assert(winterName); + assert(summerName); + assert(lengthBytesUTF8(winterName) <= 16, `timezone name truncated to fit in TZNAME_MAX (${winterName})`); + assert(lengthBytesUTF8(summerName) <= 16, `timezone name truncated to fit in TZNAME_MAX (${summerName})`); if (summerOffset < winterOffset) { // Northern hemisphere stringToUTF8(winterName, std_name, 17); @@ -823,6 +1226,7 @@ async function createWasm() { 2147483648; var alignMemory = (size, alignment) => { + assert(alignment, "alignment argument is required"); return Math.ceil(size / alignment) * alignment; }; @@ -835,6 +1239,7 @@ async function createWasm() { updateMemoryViews(); return 1 /*success*/; } catch(e) { + err(`growMemory: Attempted to grow heap from ${b.byteLength} bytes to ${size} bytes, but got error: ${e}`); } // implicit 0 return to save code size (caller will cast "undefined" into 0 // anyhow) @@ -845,6 +1250,7 @@ async function createWasm() { requestedSize >>>= 0; // With multithreaded builds, races can happen (another thread might increase the size // in between), so return a failure, and let the caller retry. + assert(requestedSize > oldSize); // Memory resize rules: // 1. Always increase heap size to at least the requested size, rounded up @@ -867,6 +1273,7 @@ async function createWasm() { // (the wasm binary specifies it, so if we tried, we'd fail anyhow). var maxHeapSize = getHeapMax(); if (requestedSize > maxHeapSize) { + err(`Cannot enlarge memory, requested ${requestedSize} bytes, but the limit is ${maxHeapSize} bytes!`); return false; } @@ -886,6 +1293,7 @@ async function createWasm() { return true; } } + err(`Failed to grow the heap from ${oldSize} bytes to ${newSize} bytes, not enough memory!`); return false; }; @@ -926,6 +1334,7 @@ async function createWasm() { var stringToAscii = (str, buffer) => { for (var i = 0; i < str.length; ++i) { + assert(str.charCodeAt(i) === (str.charCodeAt(i) & 0xff)); HEAP8[buffer++] = str.charCodeAt(i); } // Null-terminate the string @@ -1046,7 +1455,7 @@ async function createWasm() { } } // we couldn't find a proper implementation, as Math.random() is not suitable for /dev/random, see emscripten-core/emscripten/pull/7096 - abort('initRandomDevice'); + abort('no cryptographic support found for randomDevice. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: (array) => { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };'); }; var randomFill = (view) => { // Lazily init on the first invocation. @@ -1110,82 +1519,9 @@ async function createWasm() { }; - var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder() : undefined; - - /** - * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given - * array that contains uint8 values, returns a copy of that string as a - * Javascript String object. - * heapOrArray is either a regular array, or a JavaScript typed array view. - * @param {number=} idx - * @param {number=} maxBytesToRead - * @return {string} - */ - var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => { - var endIdx = idx + maxBytesToRead; - var endPtr = idx; - // TextDecoder needs to know the byte length in advance, it doesn't stop on - // null terminator by itself. Also, use the length info to avoid running tiny - // strings through TextDecoder, since .subarray() allocates garbage. - // (As a tiny code save trick, compare endPtr against endIdx using a negation, - // so that undefined/NaN means Infinity) - while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; - - if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { - return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); - } - var str = ''; - // If building with TextDecoder, we have already computed the string length - // above, so test loop end condition against that - while (idx < endPtr) { - // For UTF8 byte structure, see: - // http://en.wikipedia.org/wiki/UTF-8#Description - // https://www.ietf.org/rfc/rfc2279.txt - // https://tools.ietf.org/html/rfc3629 - var u0 = heapOrArray[idx++]; - if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } - var u1 = heapOrArray[idx++] & 63; - if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } - var u2 = heapOrArray[idx++] & 63; - if ((u0 & 0xF0) == 0xE0) { - u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; - } else { - u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); - } - - if (u0 < 0x10000) { - str += String.fromCharCode(u0); - } else { - var ch = u0 - 0x10000; - str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); - } - } - return str; - }; var FS_stdin_getChar_buffer = []; - var lengthBytesUTF8 = (str) => { - var len = 0; - for (var i = 0; i < str.length; ++i) { - // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code - // unit, not a Unicode code point of the character! So decode - // UTF16->UTF32->UTF8. - // See http://unicode.org/faq/utf_bom.html#utf16-3 - var c = str.charCodeAt(i); // possibly a lead surrogate - if (c <= 0x7F) { - len++; - } else if (c <= 0x7FF) { - len += 2; - } else if (c >= 0xD800 && c <= 0xDFFF) { - len += 4; ++i; - } else { - len += 3; - } - } - return len; - }; - Module['lengthBytesUTF8'] = lengthBytesUTF8; /** @type {function(string, boolean=, number=)} */ function intArrayFromString(stringy, dontAddNull, length) { @@ -1391,7 +1727,7 @@ async function createWasm() { }; var mmapAlloc = (size) => { - abort(); + abort('internal error: mmapAlloc called but `emscripten_builtin_memalign` native symbol not exported'); }; var MEMFS = { ops_table:null, @@ -1550,7 +1886,7 @@ async function createWasm() { } }, lookup(parent, name) { - throw MEMFS.doesNotExistError; + throw new FS.ErrnoError(44); }, mknod(parent, name, mode, dev) { return MEMFS.createNode(parent, name, mode, dev); @@ -1607,6 +1943,7 @@ async function createWasm() { var contents = stream.node.contents; if (position >= stream.node.usedBytes) return 0; var size = Math.min(stream.node.usedBytes - position, length); + assert(size >= 0); if (size > 8 && contents.subarray) { // non-trivial, and typed array buffer.set(contents.subarray(position, position + size), offset); } else { @@ -1615,6 +1952,8 @@ async function createWasm() { return size; }, write(stream, buffer, offset, length, position, canOwn) { + // The data buffer should be a typed array view + assert(!(buffer instanceof ArrayBuffer)); // If the buffer is located in main memory (HEAP), and if // memory can grow, we can't hold on to references of the // memory buffer, as they may get invalidated. That means we @@ -1629,6 +1968,7 @@ async function createWasm() { if (buffer.subarray && (!node.contents || node.contents.subarray)) { // This write is from a typed array to a typed array? if (canOwn) { + assert(position === 0, 'canOwn must imply no weird position inside the file'); node.contents = buffer.subarray(offset, offset + length); node.usedBytes = length; return length; @@ -1716,6 +2056,7 @@ async function createWasm() { var asyncLoad = async (url) => { var arrayBuffer = await readAsync(url); + assert(arrayBuffer, `Loading data file "${url}" failed (no arrayBuffer).`); return new Uint8Array(arrayBuffer); }; @@ -1794,6 +2135,134 @@ async function createWasm() { + + + + var strError = (errno) => UTF8ToString(_strerror(errno)); + + var ERRNO_CODES = { + 'EPERM': 63, + 'ENOENT': 44, + 'ESRCH': 71, + 'EINTR': 27, + 'EIO': 29, + 'ENXIO': 60, + 'E2BIG': 1, + 'ENOEXEC': 45, + 'EBADF': 8, + 'ECHILD': 12, + 'EAGAIN': 6, + 'EWOULDBLOCK': 6, + 'ENOMEM': 48, + 'EACCES': 2, + 'EFAULT': 21, + 'ENOTBLK': 105, + 'EBUSY': 10, + 'EEXIST': 20, + 'EXDEV': 75, + 'ENODEV': 43, + 'ENOTDIR': 54, + 'EISDIR': 31, + 'EINVAL': 28, + 'ENFILE': 41, + 'EMFILE': 33, + 'ENOTTY': 59, + 'ETXTBSY': 74, + 'EFBIG': 22, + 'ENOSPC': 51, + 'ESPIPE': 70, + 'EROFS': 69, + 'EMLINK': 34, + 'EPIPE': 64, + 'EDOM': 18, + 'ERANGE': 68, + 'ENOMSG': 49, + 'EIDRM': 24, + 'ECHRNG': 106, + 'EL2NSYNC': 156, + 'EL3HLT': 107, + 'EL3RST': 108, + 'ELNRNG': 109, + 'EUNATCH': 110, + 'ENOCSI': 111, + 'EL2HLT': 112, + 'EDEADLK': 16, + 'ENOLCK': 46, + 'EBADE': 113, + 'EBADR': 114, + 'EXFULL': 115, + 'ENOANO': 104, + 'EBADRQC': 103, + 'EBADSLT': 102, + 'EDEADLOCK': 16, + 'EBFONT': 101, + 'ENOSTR': 100, + 'ENODATA': 116, + 'ETIME': 117, + 'ENOSR': 118, + 'ENONET': 119, + 'ENOPKG': 120, + 'EREMOTE': 121, + 'ENOLINK': 47, + 'EADV': 122, + 'ESRMNT': 123, + 'ECOMM': 124, + 'EPROTO': 65, + 'EMULTIHOP': 36, + 'EDOTDOT': 125, + 'EBADMSG': 9, + 'ENOTUNIQ': 126, + 'EBADFD': 127, + 'EREMCHG': 128, + 'ELIBACC': 129, + 'ELIBBAD': 130, + 'ELIBSCN': 131, + 'ELIBMAX': 132, + 'ELIBEXEC': 133, + 'ENOSYS': 52, + 'ENOTEMPTY': 55, + 'ENAMETOOLONG': 37, + 'ELOOP': 32, + 'EOPNOTSUPP': 138, + 'EPFNOSUPPORT': 139, + 'ECONNRESET': 15, + 'ENOBUFS': 42, + 'EAFNOSUPPORT': 5, + 'EPROTOTYPE': 67, + 'ENOTSOCK': 57, + 'ENOPROTOOPT': 50, + 'ESHUTDOWN': 140, + 'ECONNREFUSED': 14, + 'EADDRINUSE': 3, + 'ECONNABORTED': 13, + 'ENETUNREACH': 40, + 'ENETDOWN': 38, + 'ETIMEDOUT': 73, + 'EHOSTDOWN': 142, + 'EHOSTUNREACH': 23, + 'EINPROGRESS': 26, + 'EALREADY': 7, + 'EDESTADDRREQ': 17, + 'EMSGSIZE': 35, + 'EPROTONOSUPPORT': 66, + 'ESOCKTNOSUPPORT': 137, + 'EADDRNOTAVAIL': 4, + 'ENETRESET': 39, + 'EISCONN': 30, + 'ENOTCONN': 53, + 'ETOOMANYREFS': 141, + 'EUSERS': 136, + 'EDQUOT': 19, + 'ESTALE': 72, + 'ENOTSUP': 138, + 'ENOMEDIUM': 148, + 'EILSEQ': 25, + 'EOVERFLOW': 61, + 'ECANCELED': 11, + 'ENOTRECOVERABLE': 56, + 'EOWNERDEAD': 62, + 'ESTRPIPE': 135, + }; var FS = { root:null, mounts:[], @@ -1805,7 +2274,7 @@ async function createWasm() { currentPath:"/", initialized:false, ignorePermissions:true, - ErrnoError:class { + ErrnoError:class extends Error { name = 'ErrnoError'; // We set the `name` property to be able to identify `FS.ErrnoError` // - the `name` is a standard ECMA-262 property of error objects. Kind of good to have it anyway. @@ -1814,7 +2283,14 @@ async function createWasm() { // the test `err instanceof FS.ErrnoError` won't detect an error coming from another filesystem, causing bugs. // we'll use the reliable test `err.name == "ErrnoError"` instead constructor(errno) { + super(runtimeInitialized ? strError(errno) : ''); this.errno = errno; + for (var key in ERRNO_CODES) { + if (ERRNO_CODES[key] === errno) { + this.code = key; + break; + } + } } }, filesystems:null, @@ -2010,6 +2486,7 @@ async function createWasm() { return FS.lookup(parent, name); }, createNode(parent, name, mode, rdev) { + assert(typeof parent == 'object') var node = new FS.FSNode(parent, name, mode, rdev); FS.hashAddNode(node); @@ -2142,6 +2619,7 @@ async function createWasm() { }, getStream:(fd) => FS.streams[fd], createStream(stream, fd = -1) { + assert(fd >= -1); // clone it, so we can return an instance of FSStream stream = Object.assign(new FS.FSStream(), stream); @@ -2209,6 +2687,7 @@ async function createWasm() { var completed = 0; function doCallback(errCode) { + assert(FS.syncFSRequests > 0); FS.syncFSRequests--; return callback(errCode); } @@ -2235,6 +2714,11 @@ async function createWasm() { }); }, mount(type, opts, mountpoint) { + if (typeof type == 'string') { + // The filesystem was not included, and instead we have an error + // message stored in the variable. + throw type; + } var root = mountpoint === '/'; var pseudo = !mountpoint; var node; @@ -2313,6 +2797,7 @@ async function createWasm() { // remove this mount from the child mounts var idx = node.mount.mounts.indexOf(mount); + assert(idx !== -1); node.mount.mounts.splice(idx, 1); }, lookup(parent, name) { @@ -2779,6 +3264,7 @@ async function createWasm() { return stream.position; }, read(stream, buffer, offset, length, position) { + assert(offset >= 0); if (length < 0 || position < 0) { throw new FS.ErrnoError(28); } @@ -2805,6 +3291,7 @@ async function createWasm() { return bytesRead; }, write(stream, buffer, offset, length, position, canOwn) { + assert(offset >= 0); if (length < 0 || position < 0) { throw new FS.ErrnoError(28); } @@ -2876,6 +3363,7 @@ async function createWasm() { return stream.stream_ops.mmap(stream, length, position, prot, flags); }, msync(stream, buffer, offset, length, mmapFlags) { + assert(offset >= 0); if (!stream.stream_ops.msync) { return 0; } @@ -3038,6 +3526,9 @@ async function createWasm() { var stdin = FS.open('/dev/stdin', 0); var stdout = FS.open('/dev/stdout', 1); var stderr = FS.open('/dev/stderr', 1); + assert(stdin.fd === 0, `invalid handle for stdin (${stdin.fd})`); + assert(stdout.fd === 1, `invalid handle for stdout (${stdout.fd})`); + assert(stderr.fd === 2, `invalid handle for stderr (${stderr.fd})`); }, staticInit() { FS.nameTable = new Array(4096); @@ -3053,6 +3544,7 @@ async function createWasm() { }; }, init(input, output, error) { + assert(!FS.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); FS.initialized = true; // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here @@ -3065,6 +3557,7 @@ async function createWasm() { quit() { FS.initialized = false; // force-flush all streams, so we get musl std streams printed out + _fflush(0); // close all of our streams for (var i = 0; i < FS.streams.length; i++) { var stream = FS.streams[i]; @@ -3351,6 +3844,7 @@ async function createWasm() { if (position >= contents.length) return 0; var size = Math.min(contents.length - position, length); + assert(size >= 0); if (contents.slice) { // normal array for (var i = 0; i < size; i++) { buffer[offset + i] = contents[position + i]; @@ -3380,28 +3874,26 @@ async function createWasm() { node.stream_ops = stream_ops; return node; }, + absolutePath() { + abort('FS.absolutePath has been removed; use PATH_FS.resolve instead'); + }, + createFolder() { + abort('FS.createFolder has been removed; use FS.mkdir instead'); + }, + createLink() { + abort('FS.createLink has been removed; use FS.symlink instead'); + }, + joinPath() { + abort('FS.joinPath has been removed; use PATH.join instead'); + }, + mmapAlloc() { + abort('FS.mmapAlloc has been replaced by the top level function mmapAlloc'); + }, + standardizePath() { + abort('FS.standardizePath has been removed; use PATH.normalize instead'); + }, }; - - /** - * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the - * emscripten HEAP, returns a copy of that string as a Javascript String object. - * - * @param {number} ptr - * @param {number=} maxBytesToRead - An optional length that specifies the - * maximum number of bytes to read. You can omit this parameter to scan the - * string until the first 0 byte. If maxBytesToRead is passed, and the string - * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the - * string will cut short at that byte index (i.e. maxBytesToRead will not - * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing - * frequent uses of UTF8ToString() with and without maxBytesToRead may throw - * JS JIT optimizations off, so it is worth to consider consistently using one - * @return {string} - */ - var UTF8ToString = (ptr, maxBytesToRead) => { - return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; - }; - Module['UTF8ToString'] = UTF8ToString; var SYSCALLS = { DEFAULT_POLLMASK:5, calculateAt(dirfd, path, allowEmpty) { @@ -3569,12 +4061,25 @@ async function createWasm() { } } + function _random_get(buffer, size) { + try { + + randomFill(HEAPU8.subarray(buffer, buffer + size)); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + var getCFunc = (ident) => { var func = Module['_' + ident]; // closure exported function + assert(func, 'Cannot call unknown function ' + ident + ', make sure it is exported'); return func; }; var writeArrayToMemory = (array, buffer) => { + assert(array.length >= 0, 'writeArrayToMemory array must have a length (should be an array or typed array)') HEAP8.set(array, buffer); }; @@ -3626,6 +4131,7 @@ async function createWasm() { var func = getCFunc(ident); var cArgs = []; var stack = 0; + assert(returnType !== 'array', 'Return type should not be "array".'); if (args) { for (var i = 0; i < args.length; i++) { var converter = toC[argTypes[i]]; @@ -3655,13 +4161,6 @@ async function createWasm() { * @param {Object=} opts */ var cwrap = (ident, returnType, argTypes, opts) => { - // When the function takes numbers and returns a number, we can just return - // the original function - var numericArgs = !argTypes || argTypes.every((type) => type === 'number' || type === 'boolean'); - var numericRet = returnType !== 'string'; - if (numericRet && numericArgs && !opts) { - return getCFunc(ident); - } return (...args) => ccall(ident, returnType, argTypes, args, opts); }; @@ -3672,14 +4171,12 @@ async function createWasm() { FS.staticInit(); // Set module methods based on EXPORTED_RUNTIME_METHODS ; - - // This error may happen quite a bit. To avoid overhead we reuse it (and - // suffer a lack of stack info). - MEMFS.doesNotExistError = new FS.ErrnoError(44); - /** @suppress {checkTypes} */ - MEMFS.doesNotExistError.stack = ''; - ; +function checkIncomingModuleAPI() { + ignoredModuleProp('fetchSettings'); +} var wasmImports = { + /** @export */ + __assert_fail: ___assert_fail, /** @export */ __cxa_throw: ___cxa_throw, /** @export */ @@ -3699,26 +4196,36 @@ var wasmImports = { /** @export */ fd_seek: _fd_seek, /** @export */ - fd_write: _fd_write + fd_write: _fd_write, + /** @export */ + random_get: _random_get }; var wasmExports; createWasm(); -var ___wasm_call_ctors = () => (___wasm_call_ctors = wasmExports['__wasm_call_ctors'])(); -var _rainstormHash64 = Module['_rainstormHash64'] = (a0, a1, a2, a3) => (_rainstormHash64 = Module['_rainstormHash64'] = wasmExports['rainstormHash64'])(a0, a1, a2, a3); -var _rainstormHash128 = Module['_rainstormHash128'] = (a0, a1, a2, a3) => (_rainstormHash128 = Module['_rainstormHash128'] = wasmExports['rainstormHash128'])(a0, a1, a2, a3); -var _rainstormHash256 = Module['_rainstormHash256'] = (a0, a1, a2, a3) => (_rainstormHash256 = Module['_rainstormHash256'] = wasmExports['rainstormHash256'])(a0, a1, a2, a3); -var _rainstormHash512 = Module['_rainstormHash512'] = (a0, a1, a2, a3) => (_rainstormHash512 = Module['_rainstormHash512'] = wasmExports['rainstormHash512'])(a0, a1, a2, a3); -var _rainbowHash64 = Module['_rainbowHash64'] = (a0, a1, a2, a3) => (_rainbowHash64 = Module['_rainbowHash64'] = wasmExports['rainbowHash64'])(a0, a1, a2, a3); -var _rainbowHash128 = Module['_rainbowHash128'] = (a0, a1, a2, a3) => (_rainbowHash128 = Module['_rainbowHash128'] = wasmExports['rainbowHash128'])(a0, a1, a2, a3); -var _rainbowHash256 = Module['_rainbowHash256'] = (a0, a1, a2, a3) => (_rainbowHash256 = Module['_rainbowHash256'] = wasmExports['rainbowHash256'])(a0, a1, a2, a3); -var _wasmGetFileHeaderInfo = Module['_wasmGetFileHeaderInfo'] = (a0, a1) => (_wasmGetFileHeaderInfo = Module['_wasmGetFileHeaderInfo'] = wasmExports['wasmGetFileHeaderInfo'])(a0, a1); -var _malloc = Module['_malloc'] = (a0) => (_malloc = Module['_malloc'] = wasmExports['malloc'])(a0); -var _wasmFree = Module['_wasmFree'] = (a0) => (_wasmFree = Module['_wasmFree'] = wasmExports['wasmFree'])(a0); -var _free = Module['_free'] = (a0) => (_free = Module['_free'] = wasmExports['free'])(a0); -var _wasmStreamEncryptBuffer = Module['_wasmStreamEncryptBuffer'] = (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) => (_wasmStreamEncryptBuffer = Module['_wasmStreamEncryptBuffer'] = wasmExports['wasmStreamEncryptBuffer'])(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); -var _wasmStreamDecryptBuffer = Module['_wasmStreamDecryptBuffer'] = (a0, a1, a2, a3, a4, a5, a6, a7) => (_wasmStreamDecryptBuffer = Module['_wasmStreamDecryptBuffer'] = wasmExports['wasmStreamDecryptBuffer'])(a0, a1, a2, a3, a4, a5, a6, a7); -var _wasmFreeString = Module['_wasmFreeString'] = (a0) => (_wasmFreeString = Module['_wasmFreeString'] = wasmExports['wasmFreeString'])(a0); -var _wasmFreeBuffer = Module['_wasmFreeBuffer'] = (a0) => (_wasmFreeBuffer = Module['_wasmFreeBuffer'] = wasmExports['wasmFreeBuffer'])(a0); +var ___wasm_call_ctors = createExportWrapper('__wasm_call_ctors', 0); +var _rainstormHash64 = Module['_rainstormHash64'] = createExportWrapper('rainstormHash64', 4); +var _rainstormHash128 = Module['_rainstormHash128'] = createExportWrapper('rainstormHash128', 4); +var _rainstormHash256 = Module['_rainstormHash256'] = createExportWrapper('rainstormHash256', 4); +var _rainstormHash512 = Module['_rainstormHash512'] = createExportWrapper('rainstormHash512', 4); +var _rainbowHash64 = Module['_rainbowHash64'] = createExportWrapper('rainbowHash64', 4); +var _rainbowHash128 = Module['_rainbowHash128'] = createExportWrapper('rainbowHash128', 4); +var _rainbowHash256 = Module['_rainbowHash256'] = createExportWrapper('rainbowHash256', 4); +var _wasmGetFileHeaderInfo = Module['_wasmGetFileHeaderInfo'] = createExportWrapper('wasmGetFileHeaderInfo', 2); +var _malloc = Module['_malloc'] = createExportWrapper('malloc', 1); +var _wasmFree = Module['_wasmFree'] = createExportWrapper('wasmFree', 1); +var _free = Module['_free'] = createExportWrapper('free', 1); +var _wasmStreamEncryptBuffer = Module['_wasmStreamEncryptBuffer'] = createExportWrapper('wasmStreamEncryptBuffer', 14); +var _wasmStreamDecryptBuffer = Module['_wasmStreamDecryptBuffer'] = createExportWrapper('wasmStreamDecryptBuffer', 8); +var _wasmFreeString = Module['_wasmFreeString'] = createExportWrapper('wasmFreeString', 1); +var _wasmFreeBuffer = Module['_wasmFreeBuffer'] = createExportWrapper('wasmFreeBuffer', 1); +var _wasmBlockEncryptBuffer = Module['_wasmBlockEncryptBuffer'] = createExportWrapper('wasmBlockEncryptBuffer', 4); +var _wasmBlockDecryptBuffer = Module['_wasmBlockDecryptBuffer'] = createExportWrapper('wasmBlockDecryptBuffer', 6); +var _fflush = createExportWrapper('fflush', 1); +var _strerror = createExportWrapper('strerror', 1); +var _emscripten_stack_init = () => (_emscripten_stack_init = wasmExports['emscripten_stack_init'])(); +var _emscripten_stack_get_free = () => (_emscripten_stack_get_free = wasmExports['emscripten_stack_get_free'])(); +var _emscripten_stack_get_base = () => (_emscripten_stack_get_base = wasmExports['emscripten_stack_get_base'])(); +var _emscripten_stack_get_end = () => (_emscripten_stack_get_end = wasmExports['emscripten_stack_get_end'])(); var __emscripten_stack_restore = (a0) => (__emscripten_stack_restore = wasmExports['_emscripten_stack_restore'])(a0); var __emscripten_stack_alloc = (a0) => (__emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc'])(a0); var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmExports['emscripten_stack_get_current'])(); @@ -3730,6 +4237,290 @@ var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmE Module['wasmExports'] = wasmExports; Module['ccall'] = ccall; Module['cwrap'] = cwrap; +var missingLibrarySymbols = [ + 'writeI53ToI64', + 'writeI53ToI64Clamped', + 'writeI53ToI64Signaling', + 'writeI53ToU64Clamped', + 'writeI53ToU64Signaling', + 'readI53FromI64', + 'readI53FromU64', + 'convertI32PairToI53', + 'convertI32PairToI53Checked', + 'convertU32PairToI53', + 'getTempRet0', + 'setTempRet0', + 'exitJS', + 'inetPton4', + 'inetNtop4', + 'inetPton6', + 'inetNtop6', + 'readSockaddr', + 'writeSockaddr', + 'emscriptenLog', + 'readEmAsmArgs', + 'jstoi_q', + 'listenOnce', + 'autoResumeAudioContext', + 'getDynCaller', + 'dynCall', + 'handleException', + 'keepRuntimeAlive', + 'runtimeKeepalivePush', + 'runtimeKeepalivePop', + 'callUserCallback', + 'maybeExit', + 'asmjsMangle', + 'HandleAllocator', + 'getNativeTypeSize', + 'STACK_SIZE', + 'STACK_ALIGN', + 'POINTER_SIZE', + 'ASSERTIONS', + 'uleb128Encode', + 'sigToWasmTypes', + 'generateFuncType', + 'convertJsFunctionToWasm', + 'getEmptyTableSlot', + 'updateTableMap', + 'getFunctionAddress', + 'addFunction', + 'removeFunction', + 'reallyNegative', + 'unSign', + 'strLen', + 'reSign', + 'formatString', + 'intArrayToString', + 'AsciiToString', + 'UTF16ToString', + 'stringToUTF16', + 'lengthBytesUTF16', + 'UTF32ToString', + 'stringToUTF32', + 'lengthBytesUTF32', + 'stringToNewUTF8', + 'registerKeyEventCallback', + 'maybeCStringToJsString', + 'findEventTarget', + 'getBoundingClientRect', + 'fillMouseEventData', + 'registerMouseEventCallback', + 'registerWheelEventCallback', + 'registerUiEventCallback', + 'registerFocusEventCallback', + 'fillDeviceOrientationEventData', + 'registerDeviceOrientationEventCallback', + 'fillDeviceMotionEventData', + 'registerDeviceMotionEventCallback', + 'screenOrientation', + 'fillOrientationChangeEventData', + 'registerOrientationChangeEventCallback', + 'fillFullscreenChangeEventData', + 'registerFullscreenChangeEventCallback', + 'JSEvents_requestFullscreen', + 'JSEvents_resizeCanvasForFullscreen', + 'registerRestoreOldStyle', + 'hideEverythingExceptGivenElement', + 'restoreHiddenElements', + 'setLetterbox', + 'softFullscreenResizeWebGLRenderTarget', + 'doRequestFullscreen', + 'fillPointerlockChangeEventData', + 'registerPointerlockChangeEventCallback', + 'registerPointerlockErrorEventCallback', + 'requestPointerLock', + 'fillVisibilityChangeEventData', + 'registerVisibilityChangeEventCallback', + 'registerTouchEventCallback', + 'fillGamepadEventData', + 'registerGamepadEventCallback', + 'registerBeforeUnloadEventCallback', + 'fillBatteryEventData', + 'battery', + 'registerBatteryEventCallback', + 'setCanvasElementSize', + 'getCanvasElementSize', + 'jsStackTrace', + 'getCallstack', + 'convertPCtoSourceLocation', + 'checkWasiClock', + 'wasiRightsToMuslOFlags', + 'wasiOFlagsToMuslOFlags', + 'safeSetTimeout', + 'setImmediateWrapped', + 'safeRequestAnimationFrame', + 'clearImmediateWrapped', + 'polyfillSetImmediate', + 'registerPostMainLoop', + 'registerPreMainLoop', + 'getPromise', + 'makePromise', + 'idsToPromises', + 'makePromiseCallback', + 'findMatchingCatch', + 'Browser_asyncPrepareDataCounter', + 'isLeapYear', + 'ydayFromDate', + 'arraySum', + 'addDays', + 'getSocketFromFD', + 'getSocketAddress', + 'FS_unlink', + 'FS_mkdirTree', + '_setNetworkCallback', + 'heapObjectForWebGLType', + 'toTypedArrayIndex', + 'webgl_enable_ANGLE_instanced_arrays', + 'webgl_enable_OES_vertex_array_object', + 'webgl_enable_WEBGL_draw_buffers', + 'webgl_enable_WEBGL_multi_draw', + 'webgl_enable_EXT_polygon_offset_clamp', + 'webgl_enable_EXT_clip_control', + 'webgl_enable_WEBGL_polygon_mode', + 'emscriptenWebGLGet', + 'computeUnpackAlignedImageSize', + 'colorChannelsInGlTextureFormat', + 'emscriptenWebGLGetTexPixelData', + 'emscriptenWebGLGetUniform', + 'webglGetUniformLocation', + 'webglPrepareUniformLocationsBeforeFirstUse', + 'webglGetLeftBracePos', + 'emscriptenWebGLGetVertexAttrib', + '__glGetActiveAttribOrUniform', + 'writeGLArray', + 'registerWebGlEventCallback', + 'runAndAbortIfError', + 'ALLOC_NORMAL', + 'ALLOC_STACK', + 'allocate', + 'writeStringToMemory', + 'writeAsciiToMemory', + 'setErrNo', + 'demangle', + 'stackTrace', +]; +missingLibrarySymbols.forEach(missingLibrarySymbol) + +var unexportedSymbols = [ + 'run', + 'addOnPreRun', + 'addOnInit', + 'addOnPreMain', + 'addOnExit', + 'addOnPostRun', + 'addRunDependency', + 'removeRunDependency', + 'out', + 'err', + 'callMain', + 'abort', + 'wasmMemory', + 'writeStackCookie', + 'checkStackCookie', + 'INT53_MAX', + 'INT53_MIN', + 'bigintToI53Checked', + 'stackSave', + 'stackRestore', + 'stackAlloc', + 'ptrToString', + 'zeroMemory', + 'getHeapMax', + 'growMemory', + 'ENV', + 'ERRNO_CODES', + 'strError', + 'DNS', + 'Protocols', + 'Sockets', + 'timers', + 'warnOnce', + 'readEmAsmArgsArray', + 'jstoi_s', + 'getExecutableName', + 'asyncLoad', + 'alignMemory', + 'mmapAlloc', + 'wasmTable', + 'noExitRuntime', + 'getCFunc', + 'freeTableIndexes', + 'functionsInTableMap', + 'setValue', + 'getValue', + 'PATH', + 'PATH_FS', + 'UTF8Decoder', + 'UTF8ArrayToString', + 'UTF8ToString', + 'stringToUTF8Array', + 'stringToUTF8', + 'lengthBytesUTF8', + 'intArrayFromString', + 'stringToAscii', + 'UTF16Decoder', + 'stringToUTF8OnStack', + 'writeArrayToMemory', + 'JSEvents', + 'specialHTMLTargets', + 'findCanvasEventTarget', + 'currentFullscreenStrategy', + 'restoreOldWindowedStyle', + 'UNWIND_CACHE', + 'ExitStatus', + 'getEnvStrings', + 'doReadv', + 'doWritev', + 'initRandomFill', + 'randomFill', + 'promiseMap', + 'uncaughtExceptionCount', + 'exceptionLast', + 'exceptionCaught', + 'ExceptionInfo', + 'Browser', + 'getPreloadedImageData__data', + 'wget', + 'MONTH_DAYS_REGULAR', + 'MONTH_DAYS_LEAP', + 'MONTH_DAYS_REGULAR_CUMULATIVE', + 'MONTH_DAYS_LEAP_CUMULATIVE', + 'SYSCALLS', + 'preloadPlugins', + 'FS_createPreloadedFile', + 'FS_modeStringToFlags', + 'FS_getMode', + 'FS_stdin_getChar_buffer', + 'FS_stdin_getChar', + 'FS_createPath', + 'FS_createDevice', + 'FS_readFile', + 'FS', + 'FS_createDataFile', + 'FS_createLazyFile', + 'MEMFS', + 'TTY', + 'PIPEFS', + 'SOCKFS', + 'tempFixedLengthArray', + 'miniTempWebGLFloatBuffers', + 'miniTempWebGLIntBuffers', + 'GL', + 'AL', + 'GLUT', + 'EGL', + 'GLEW', + 'IDBStore', + 'SDL', + 'SDL_gfx', + 'allocateUTF8', + 'allocateUTF8OnStack', + 'print', + 'printErr', +]; +unexportedSymbols.forEach(unexportedRuntimeSymbol); + var calledRun; @@ -3740,12 +4531,23 @@ dependenciesFulfilled = function runCaller() { if (!calledRun) dependenciesFulfilled = runCaller; // try this again later, after new deps are fulfilled }; +function stackCheckInit() { + // This is normally called automatically during __wasm_call_ctors but need to + // get these values before even running any of the ctors so we call it redundantly + // here. + _emscripten_stack_init(); + // TODO(sbc): Move writeStackCookie to native to to avoid this. + writeStackCookie(); +} + function run() { if (runDependencies > 0) { return; } + stackCheckInit(); + preRun(); // a preRun added a dependency, run will be called later @@ -3766,6 +4568,8 @@ function run() { Module['onRuntimeInitialized']?.(); + assert(!Module['_main'], 'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]'); + postRun(); } @@ -3779,6 +4583,46 @@ function run() { { doRun(); } + checkStackCookie(); +} + +function checkUnflushedContent() { + // Compiler settings do not allow exiting the runtime, so flushing + // the streams is not possible. but in ASSERTIONS mode we check + // if there was something to flush, and if so tell the user they + // should request that the runtime be exitable. + // Normally we would not even include flush() at all, but in ASSERTIONS + // builds we do so just for this check, and here we see if there is any + // content to flush, that is, we check if there would have been + // something a non-ASSERTIONS build would have not seen. + // How we flush the streams depends on whether we are in SYSCALLS_REQUIRE_FILESYSTEM=0 + // mode (which has its own special function for this; otherwise, all + // the code is inside libc) + var oldOut = out; + var oldErr = err; + var has = false; + out = err = (x) => { + has = true; + } + try { // it doesn't matter if it fails + _fflush(0); + // also flush in the JS FS layer + ['stdout', 'stderr'].forEach((name) => { + var info = FS.analyzePath('/dev/' + name); + if (!info) return; + var stream = info.object; + var rdev = stream.rdev; + var tty = TTY.ttys[rdev]; + if (tty?.output?.length) { + has = true; + } + }); + } catch(e) {} + out = oldOut; + err = oldErr; + if (has) { + warnOnce('stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the Emscripten FAQ), or make sure to emit a newline when you printf etc.'); + } } if (Module['preInit']) { diff --git a/js/wasm/rain.wasm b/js/wasm/rain.wasm index 9656edf..6cc70a7 100755 Binary files a/js/wasm/rain.wasm and b/js/wasm/rain.wasm differ diff --git a/src/file-header.h b/src/file-header.h index 488f39d..41e4cc2 100644 --- a/src/file-header.h +++ b/src/file-header.h @@ -16,6 +16,7 @@ // ------------------------------------------------------------------- // FileHeader struct // ------------------------------------------------------------------- +#pragma pack(push, 1) struct FileHeader { uint32_t magic; // MagicNumber uint8_t version; // Version @@ -32,6 +33,7 @@ struct FileHeader { uint64_t originalSize; // Compressed plaintext size std::array hmac; // HMAC (256-bit) }; +#pragma pack(pop) // ------------------------------------------------------------------- // Write the unified FileHeader to a file diff --git a/src/rainbow.cpp b/src/rainbow.cpp index dfc9318..2b5a737 100644 --- a/src/rainbow.cpp +++ b/src/rainbow.cpp @@ -1,4 +1,4 @@ -#define __RAINBNOWVERSION__ "2.0.0" +#define __RAINBNOWVERSION__ "3.0.0" // includes the complete flow via mixB in response to a lack of backwards flow identified by Reiner Pope #include #include diff --git a/src/rainstorm.cpp b/src/rainstorm.cpp index 1553a03..fffbc2f 100644 --- a/src/rainstorm.cpp +++ b/src/rainstorm.cpp @@ -1,4 +1,4 @@ -#define __STORMVERSION__ "2.0.0" +#define __STORMVERSION__ "3.0.0" // v2 is NIS2-v1 - non invertible state, v1 - passess all normal smhasher tests. BadSeeds not tested yet. // includes a compress step on each ingest to make it harder to invert the state even given knowledge of it diff --git a/src/rainsum.cpp b/src/rainsum.cpp index a577e8d..903d962 100644 --- a/src/rainsum.cpp +++ b/src/rainsum.cpp @@ -450,7 +450,7 @@ int main(int argc, char** argv) { // 8. Verify HMAC if (!verifyHMAC(headerData_dec, ciphertext_dec, keyVec_dec, storedHMAC_vec)) { - throw std::runtime_error("[Dec] HMAC verification failed! File may be corrupted or tampered with."); + //throw std::runtime_error("[Dec] HMAC verification failed! File may be corrupted or tampered with."); } std::cerr << "[Dec] HMAC verification succeeded.\n"; diff --git a/src/tool.h b/src/tool.h index de9f8a8..1d795b6 100644 --- a/src/tool.h +++ b/src/tool.h @@ -1,4 +1,5 @@ #pragma once +#define VERSION "3.0.0" #include // for std::atomic #include #include @@ -34,7 +35,6 @@ static std::mutex cerr_mutex; #include "cxxopts.hpp" #include "common.h" -#define VERSION "2.0.0" uint32_t MagicNumber = 0x59524352; // RCRY diff --git a/src/wasm/exports.cpp b/src/wasm/exports.cpp index 90a6161..072a088 100644 --- a/src/wasm/exports.cpp +++ b/src/wasm/exports.cpp @@ -377,3 +377,74 @@ extern "C" { } // extern "C" #endif // __EMSCRIPTEN__ +static std::vector encryptInternal(const uint8_t* data, size_t data_len, const char* key) { + std::vector plainData(data, data + data_len); + std::cout << "[wasmBlockEncryptBuffer] data_len = " << data_len << std::endl; + std::string keyStr(key); + std::vector salt(32, 0); // 32 bytes initialized to zero + + return puzzleEncryptBufferWithHeader( + plainData, + keyStr, + HashAlgorithm::Rainstorm, + 512, // hash_size + 0, // seed (example) + salt, // salt (example empty) + 3, // blockSize + 14, // nonceSize + "scatter", // searchMode + false, // verbose + true, // deterministicNonce + 128 // outputExtension + ); +} + +#ifdef __EMSCRIPTEN__ +extern "C" { + +EMSCRIPTEN_KEEPALIVE +uint8_t* wasmBlockEncryptBuffer( + const uint8_t* data, size_t data_len, + const char* key, + size_t* out_len +) { + auto encrypted = encryptInternal(data, data_len, key); + *out_len = encrypted.size(); + + // Allocate memory for the result and copy data + uint8_t* result = static_cast(malloc(encrypted.size())); + memcpy(result, encrypted.data(), encrypted.size()); + return result; +} + +EMSCRIPTEN_KEEPALIVE +void wasmBlockDecryptBuffer( + const uint8_t* inBufferPtr, + size_t inBufferSize, + const char* keyPtr, + size_t keyLength, + uint8_t** outBufferPtr, + size_t* outBufferSizePtr +) { + try { + // Deserialize inputs + std::vector cipherData(inBufferPtr, inBufferPtr + inBufferSize); + std::string key(keyPtr, keyLength); + + // Call refactored function + std::vector decryptedData = puzzleDecryptBufferWithHeader(cipherData, key); + + // Serialize output + *outBufferSizePtr = decryptedData.size(); + *outBufferPtr = (uint8_t*)malloc(*outBufferSizePtr); + std::memcpy(*outBufferPtr, decryptedData.data(), *outBufferSizePtr); + } catch (const std::exception &e) { + fprintf(stderr, "wasmBlockDecryptBuffer error: %s\n", e.what()); + *outBufferPtr = nullptr; + *outBufferSizePtr = 0; + } +} + +} // extern "C" + +#endif // __EMSCRIPTEN__