From 5b81cba7c8640d1b2ec20736e1e1cbbb95ff7904 Mon Sep 17 00:00:00 2001 From: Ludovic 'Archivist' Lagouardette Date: Tue, 18 Jul 2023 18:54:35 +0200 Subject: [PATCH] Added some cryptographic bases and made testing function preconditions easier --- CMakeLists.txt | 2 +- include/UserScript.h | 1 + include/UserScriptRequire.h | 147 ++++++++++++++++++ include/UserScriptWizardry.h | 12 ++ script_exe/main.cpp | 12 +- src/generator.cpp | 6 +- src/interpreter.cpp | 16 +- src/std/array.cpp | 210 +++++++++++++------------ src/std/crypto.cpp | 288 +++++++++++++++++++++++++++++++++++ 9 files changed, 569 insertions(+), 125 deletions(-) create mode 100644 include/UserScriptRequire.h create mode 100644 include/UserScriptWizardry.h create mode 100644 src/std/crypto.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6be54fe..1f1e0ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ add_library(UserScript STATIC src/generator.cpp src/interpreter.cpp src/lex.cpp - src/parse.cpp src/std/array.cpp) + src/parse.cpp src/std/array.cpp src/std/crypto.cpp include/UserScriptRequire.h include/UserScriptWizardry.h) target_include_directories(UserScript PUBLIC include) include_directories(priv_include) diff --git a/include/UserScript.h b/include/UserScript.h index 7320579..9f67725 100644 --- a/include/UserScript.h +++ b/include/UserScript.h @@ -58,4 +58,5 @@ namespace scripting { std::unique_ptr prepare_interpreter(const std::string& code); std::unique_ptr register_array_lib(std::unique_ptr target, bool recursive_arrays = false, int32_t size_limit = 1024); + std::unique_ptr register_crypto_lib(std::unique_ptr target, int32_t array_size_limit = 1024, int32_t string_size_limit = 1024); } \ No newline at end of file diff --git a/include/UserScriptRequire.h b/include/UserScriptRequire.h new file mode 100644 index 0000000..7c7ef7d --- /dev/null +++ b/include/UserScriptRequire.h @@ -0,0 +1,147 @@ +#pragma once +#include "UserScript.h" +#include "UserScriptWizardry.h" +#include +#include +#include + +/*template +struct verifier_base { + static constexpr size_t argument_id = arg_number; +};*/ + +namespace details { + template + struct TypeVerifier { + static constexpr size_t argument_id = argument_counter; + + bool verify(scripting::UserScript* self, std::vector& args) { + if(argument_id >= args.size()) return false; + if(args.size() -1 -argument_id < 0) return false; + auto& argument = args[args.size() -1 -argument_id]; + std::optional> v = std::visit( + wizardry::overloaded{ + [&](scripting::script_variable& v) -> std::optional> {return self->getValue(v.name);}, + [&](scripting::script_value& v) -> std::optional> {return v;}, + }, + argument + ); + if(not v) return false; + return std::visit(wizardry::overloaded{ + [](T& ) {return true;}, + [](auto&) {return false;} + }, v.value().get()); + } + }; + template + struct VariableVerifier { + static constexpr size_t argument_id = argument_counter; + + bool verify(scripting::UserScript* self, std::vector& args) { + if(argument_id >= args.size()) return false; + if(args.size() -1 -argument_id < 0) return false; + auto& argument = args[args.size() -1 -argument_id]; + return std::visit( + wizardry::overloaded{ + [&](scripting::script_variable& v) -> bool {return self->getValue(v.name).has_value();}, + [&](scripting::script_value& v) {return false;}, + }, + argument + ); + } + }; + + template + struct NoArrays { + bool verify(scripting::UserScript* self, std::vector args) { + for(auto& elem : std::ranges::reverse_view(args) | std::ranges::views::drop(skip)) { + std::optional> v; + std::visit( + wizardry::overloaded{ + [&](scripting::script_variable& n) {v = self->getValue(n.name);}, + [&](scripting::script_value& n) {v = n;}, + }, + elem + ); + if(not v) return false; + if(std::visit(wizardry::overloaded{ + [](scripting::array&) {return true;}, + [](auto&) {return false;} + }, v.value().get())) { + return false; + } + } + return true; + } + }; + + template + struct SizeEquals { + bool verify(scripting::UserScript*, std::vector args) { + return args.size() == sz; + } + }; + + template + struct SizeAtLeast { + bool verify(scripting::UserScript*, std::vector args) { + return args.size() >= sz; + } + }; + + template + struct OctetArrayVerifier { + static constexpr size_t argument_id = argument_counter; + + bool verify(scripting::UserScript* self, std::vector& args) { + if(argument_id >= args.size()) return false; + if(args.size() -1 -argument_id < 0) return false; + auto& argument = args[args.size() -1 -argument_id]; + std::optional> v; + std::visit( + wizardry::overloaded{ + [&](scripting::script_variable& elem) {v = self->getValue(elem.name);}, + [&](scripting::script_value& elem) {v = elem;} + }, + argument + ); + if(not v) return false; + return std::visit( + wizardry::overloaded{ + [](scripting::array& ary) -> bool { + for(auto& elem : ary.value) { + if(std::holds_alternative(elem)) { + if(auto& byte = std::get(elem); byte < 0 || byte > 255) { + return false; + } + } else { + return false; + } + } + return true; + }, + [] (auto&) -> bool {return false;} + }, + v.value().get() + ); + } + }; +} + +template +struct Verify; + +template +struct Verify { + bool verify(scripting::UserScript* self, std::vector& args) { + return n{}.verify(self, args) && Verify{}.verify(self, args); + } +}; + +template<> +struct Verify<> { + bool verify(scripting::UserScript*, const std::vector&) { + return true; + } +}; + diff --git a/include/UserScriptWizardry.h b/include/UserScriptWizardry.h new file mode 100644 index 0000000..3aabd05 --- /dev/null +++ b/include/UserScriptWizardry.h @@ -0,0 +1,12 @@ +#pragma once + +namespace wizardry { + // taken from cppreference: https://en.cppreference.com/w/cpp/utility/variant/visit + template + struct overloaded : Ts ... { + using Ts::operator()...; + }; + // explicit deduction guide (not needed as of C++20) + template + overloaded(Ts...) -> overloaded; +} \ No newline at end of file diff --git a/script_exe/main.cpp b/script_exe/main.cpp index b843045..e4fcc71 100644 --- a/script_exe/main.cpp +++ b/script_exe/main.cpp @@ -256,7 +256,11 @@ void immediate_interactive() { engine->registerFunction("exit", std::make_unique(should_exit)); engine->registerFunction("set", std::make_unique()); - engine = scripting::register_array_lib(std::move(engine), true, 4096); + constexpr size_t array_limit = 4096; + constexpr size_t string_limit = 4096; + + engine = scripting::register_array_lib(std::move(engine), true, array_limit); + engine = scripting::register_crypto_lib(std::move(engine), array_limit, string_limit); engine->registerFunction("print", std::make_unique(std::cout)); while (not should_exit) { @@ -288,7 +292,11 @@ void exec(std::span args) { engine->registerFunction("terminate", std::make_unique()); engine->registerFunction("set", std::make_unique()); - engine = scripting::register_array_lib(std::move(engine), true, 4096); + constexpr size_t array_limit = 4096; + constexpr size_t string_limit = 4096; + + engine = scripting::register_array_lib(std::move(engine), true, array_limit); + engine = scripting::register_crypto_lib(std::move(engine), array_limit, string_limit); engine->registerFunction("print", std::make_unique(std::cout)); bool exit = false; diff --git a/src/generator.cpp b/src/generator.cpp index 4cf0574..10f44a5 100644 --- a/src/generator.cpp +++ b/src/generator.cpp @@ -1,3 +1,5 @@ +#include + #include "UserScript/interpreter.h" namespace scripting { @@ -97,8 +99,8 @@ namespace scripting { void handle(std::vector &ctx, std::vector &errors, ast::command_expression &cmd) { - for (auto it = cmd.arguments.rbegin(); it != cmd.arguments.rend(); ++it) { - std::visit([&](auto &v) { handle(ctx, errors, *v); }, (*it)->contents); + for (auto& argument : std::ranges::reverse_view(cmd.arguments)) { + std::visit([&](auto &v) { handle(ctx, errors, *v); }, argument->contents); } ctx.push_back( ByteCodeInterpreter::operand{.element = ByteCodeInterpreter::function_tag{.name = cmd.name.value, .arity = cmd.arguments.size()}, .location = cmd.location}); diff --git a/src/interpreter.cpp b/src/interpreter.cpp index d692382..2310b1d 100644 --- a/src/interpreter.cpp +++ b/src/interpreter.cpp @@ -1,4 +1,5 @@ #include "UserScript/interpreter.h" +#include "UserScriptWizardry.h" namespace scripting { void to_null(script_value& value, auto on_value, auto on_error) { @@ -30,17 +31,6 @@ namespace scripting { } } - namespace wizardry { - // taken from cppreference: https://en.cppreference.com/w/cpp/utility/variant/visit - template - struct overloaded : Ts ... { - using Ts::operator()...; - }; - // explicit deduction guide (not needed as of C++20) - template - overloaded(Ts...) -> overloaded; - } - bool ByteCodeInterpreter::step(std::optional& error) { if(instruction_ptr >= bytecode.size()) return true; @@ -49,11 +39,11 @@ namespace scripting { bool ret = std::visit(wizardry::overloaded{ [&](script_value& v){ - execution_stack.push_back(v); + execution_stack.emplace_back(v); return false; }, [&](variable_tag& v){ - execution_stack.push_back(script_variable{.name = v.name}); + execution_stack.emplace_back(script_variable{.name = v.name}); return false; }, [&](ByteCodeInterpreter::operator_t& v){ diff --git a/src/std/array.cpp b/src/std/array.cpp index 0045088..1bf5b6a 100644 --- a/src/std/array.cpp +++ b/src/std/array.cpp @@ -1,8 +1,8 @@ #include "UserScript.h" +#include "UserScriptRequire.h" #include #include #include -#include using interpreter = decltype(scripting::prepare_interpreter({})); using namespace scripting; @@ -18,28 +18,16 @@ struct fn_array final : public scripting::function_impl { std::optional apply(UserScript* self, std::vector n, std::optional& error) final { array ary; - if(not can_contain_arrays) { - if(std::any_of(n.begin(), n.end(), [&](argument& arg) { - if(std::holds_alternative(arg)) { - return true; - } else { - const auto& val = self->resolve(std::get(arg).name); - if(not can_contain_arrays && std::holds_alternative(val)) { - return true; - } - } - return false; - })) { - error = script_error{ - .message = "/array: arrays cannot contain other arrays" - }; - return std::nullopt; - } + if(not can_contain_arrays && details::NoArrays<0>{}.verify(self, n)) { + error = script_error{ + .message = "/array: arrays cannot contain other arrays" + }; + return std::nullopt; } if(n.size() > std::max(0, size_limit)) { error = script_error{ - .message = "/array: arrays cannot contain other arrays" + .message = "/array: arrays exceeds max size" }; return std::nullopt; } @@ -62,7 +50,9 @@ struct fn_array_reverse final : public scripting::function_impl { fn_array_reverse() = default; std::optional apply(UserScript* self, std::vector n, std::optional& error) final { - if(n.size() != 1) { + using verifier = Verify>; + + if(verifier{}.verify(self, n)) { error = script_error{ .message = "/array_reverse takes exactly 1 argument of type array" }; @@ -78,13 +68,6 @@ struct fn_array_reverse final : public scripting::function_impl { target = self->resolve(std::get(arg).name); } - if(not std::holds_alternative(target)) { - error = script_error{ - .message = "/array_reverse takes exactly 1 argument of type array" - }; - return std::nullopt; - } - auto& ary = std::get(target); std::reverse(ary.value.begin(), ary.value.end()); @@ -101,20 +84,19 @@ struct fn_array_append final : public scripting::function_impl { const bool can_contain_arrays; fn_array_append(std::string _name, int32_t _size_limit, bool _can_contain_arrays) - : name(std::move(_name)) - , size_limit(_size_limit) - , can_contain_arrays(_can_contain_arrays) + : name(std::move(_name)) + , size_limit(_size_limit) + , can_contain_arrays(_can_contain_arrays) {} std::optional apply(UserScript* self, std::vector n, std::optional& error) final { - if(n.size() < 1) { - error = script_error{ - .message = "/array_append: must provide an array variable as first argument" - }; - return std::nullopt; - } + using verifier = Verify< + details::TypeVerifier<0, array>, + details::VariableVerifier<0>, + details::SizeAtLeast<1> + >; - if(std::holds_alternative(n.front())) { + if(verifier{}.verify(self, n) && can_contain_arrays || details::NoArrays<1>{}.verify(self, n)) { error = script_error{ .message = "/array_append: must provide an array variable as first argument" }; @@ -124,41 +106,8 @@ struct fn_array_append final : public scripting::function_impl { auto target = self->getValue(std::get(n.back()).name); n.pop_back(); - if(not target) { - error = script_error{ - .message = "/array_append: provided variable name is undefined" - }; - return std::nullopt; - } - - if(not std::holds_alternative(target.value().get())) { - error = script_error{ - .message = "/array_append: provided variable is not an array" - }; - return std::nullopt; - } - auto& ary = std::get(target.value().get()).value; - if(not can_contain_arrays) { - if(std::any_of(n.begin(), n.end(), [&](argument& arg) { - if(std::holds_alternative(arg)) { - return true; - } else { - const auto& val = self->resolve(std::get(arg).name); - if(not can_contain_arrays && std::holds_alternative(val)) { - return true; - } - } - return false; - })) { - error = script_error{ - .message = "/array_append: arrays cannot contain other arrays" - }; - return std::nullopt; - } - } - if(n.size() + ary.size() > std::max(0, size_limit)) { error = script_error{ .message = "/array_append: array would exceed size limit" @@ -180,13 +129,70 @@ struct fn_array_append final : public scripting::function_impl { ~fn_array_append() final = default; }; +struct fn_array_prepend final : public scripting::function_impl { + const std::string name; + const int32_t size_limit; + const bool can_contain_arrays; + + fn_array_prepend(std::string _name, int32_t _size_limit, bool _can_contain_arrays) + : name(std::move(_name)) + , size_limit(_size_limit) + , can_contain_arrays(_can_contain_arrays) + {} + + std::optional apply(UserScript* self, std::vector n, std::optional& error) final { + using verifier = Verify< + details::TypeVerifier<0, array>, + details::VariableVerifier<0>, + details::SizeEquals<2> + >; + + if((verifier{}.verify(self, n)) && (can_contain_arrays || details::NoArrays<1>{}.verify(self, n))) { + error = script_error{ + .message = "/array_append: must provide an array variable as first argument" + }; + return std::nullopt; + } + + auto target = self->getValue(std::get(n.back()).name); + n.pop_back(); + + auto& ary = std::get(target.value().get()).value; + + if(n.size() + ary.size() > std::max(0, size_limit)) { + error = script_error{ + .message = name + ": array would exceed size limit" + }; + return script_value{0}; + } + + if(std::holds_alternative(n.front())) { + ary.push_front(std::get(n.front())); + } else { + ary.push_front(self->resolve(std::get(n.front()).name)); + } + + return script_value{1}; + } + + ~fn_array_prepend() final = default; +}; + struct fn_array_pop final : public scripting::function_impl { - fn_array_pop() = default; + std::string name; + + fn_array_pop(std::string _name) : name(std::move(_name)) {} std::optional apply(UserScript* self, std::vector n, std::optional& error) final { - if(n.size() != 1) { + using verifier = Verify< + details::TypeVerifier<0, array>, + details::VariableVerifier<0>, + details::SizeEquals<1> + >; + + if(verifier{}.verify(self, n)) { error = script_error{ - .message = "/array_pop takes exactly 1 argument of type array" + .message = name + " takes exactly 1 argument of type array" }; return std::nullopt; } @@ -200,13 +206,6 @@ struct fn_array_pop final : public scripting::function_impl { target = self->resolve(std::get(arg).name); } - if(not std::holds_alternative(target)) { - error = script_error{ - .message = "/array_pop takes exactly 1 argument of type array" - }; - return std::nullopt; - } - auto& ary = std::get(target); if(ary.value.empty()) { @@ -226,8 +225,13 @@ struct fn_array_pop final : public scripting::function_impl { struct fn_array_size final : public scripting::function_impl { fn_array_size() = default; - std::optional apply(UserScript* self, std::vector n, std::optional& error) final { - if(n.size() != 1) { + std::optional apply(UserScript* self, std::vector n, std::optional& error) final {using verifier = Verify< + details::TypeVerifier<0, array>, + details::VariableVerifier<0>, + details::SizeEquals<1> + >; + + if(verifier{}.verify(self, n)) { error = script_error{ .message = "/array_size takes exactly 1 argument of type array" }; @@ -243,13 +247,6 @@ struct fn_array_size final : public scripting::function_impl { target = self->resolve(std::get(arg).name); } - if(not std::holds_alternative(target)) { - error = script_error{ - .message = "/array_size takes exactly 1 argument of type array" - }; - return std::nullopt; - } - return static_cast(std::get(target).value.size()); } @@ -262,7 +259,20 @@ struct fn_array_index final : public scripting::function_impl { std::optional apply(UserScript* self, std::vector n, std::optional& error) final { if(n.size() != 2) { error = script_error{ - .message = "/array_index takes exactly 1 argument of type array followed by an integer, provided a different amount" + .message = "/array_size takes exactly 2 argument of type (array, integer)" + }; + return std::nullopt; + } + + using verifier = Verify< + details::TypeVerifier<0, array>, + details::VariableVerifier<0>, + details::TypeVerifier<1, int32_t> + >; + + if(verifier{}.verify(self, n)) { + error = script_error{ + .message = "/array_index takes exactly 2 argument of type (array, integer)" }; return std::nullopt; } @@ -276,13 +286,6 @@ struct fn_array_index final : public scripting::function_impl { target = self->resolve(std::get(arg).name); } - if(not std::holds_alternative(target)) { - error = script_error{ - .message = "/array_index takes exactly 1 argument of type array followed by an integer, argument 1 is not an array" - }; - return std::nullopt; - } - auto& idx_arg = n.front(); script_value idx; @@ -292,13 +295,6 @@ struct fn_array_index final : public scripting::function_impl { idx = self->resolve(std::get(idx_arg).name); } - if(not std::holds_alternative(idx)) { - error = script_error{ - .message = "/array_index takes exactly 1 argument of type array followed by an integer, argument 2 is not an integer" - }; - return std::nullopt; - } - if(static_cast(std::get(target).value.size()) <= std::get(idx)) { error = script_error{ .message = "/array_index index must be smaller that the array size, the first element of the array has index 0" @@ -406,17 +402,17 @@ struct fn_array_set final : public scripting::function_impl { }; namespace scripting { - interpreter register_array_lib(interpreter target, bool recursive_arrays, int32_t size_limit) { target->registerFunction("array", std::make_unique(size_limit, recursive_arrays)); target->registerFunction("array_reverse", std::make_unique()); target->registerFunction("array_append", std::make_unique("array_append", size_limit, recursive_arrays)); target->registerFunction("array_push", std::make_unique("array_push", size_limit, recursive_arrays)); - target->registerFunction("array_pop", std::make_unique()); + target->registerFunction("array_pop", std::make_unique("array_pop")); target->registerFunction("array_size", std::make_unique()); target->registerFunction("array_index", std::make_unique()); target->registerFunction("array_set", std::make_unique()); + target->registerFunction("queue_enqueue", std::make_unique("queue_enqueue", size_limit, recursive_arrays)); + target->registerFunction("queue_dequeue", std::make_unique("queue_dequeue")); return std::move(target); } - } \ No newline at end of file diff --git a/src/std/crypto.cpp b/src/std/crypto.cpp new file mode 100644 index 0000000..eb173de --- /dev/null +++ b/src/std/crypto.cpp @@ -0,0 +1,288 @@ +#include "UserScript.h" +#include +#include +#include +#include "UserScriptWizardry.h" +#include "UserScriptRequire.h" + +using interpreter = decltype(scripting::prepare_interpreter({})); +using namespace scripting; + +namespace bad_cryptography { + + struct encode_n_parameters { + uint8_t spin; + uint8_t clash; + uint8_t maim; + }; + + static constexpr uint8_t encode_n_1(uint8_t value, const encode_n_parameters ¶ms) { + value += 42; + value ^= 0b01010101; + value += params.clash; + value = std::rotl(value, params.spin); + value ^= 0b10101010; + value ^= params.maim; + return value; + } + + static constexpr uint8_t decode_n_1(uint8_t value, const encode_n_parameters ¶ms) { + value ^= params.maim; + value ^= 0b10101010; + value = std::rotl(value, -params.spin); + value -= params.clash; + value ^= 0b01010101; + value -= 42; + return value; + } + + namespace eliminated_001 { + constexpr encode_n_parameters params{.spin = 3, .clash = 17, .maim = 54}; + static_assert(0 == decode_n_1(encode_n_1(0, params), params)); + static_assert(10 == decode_n_1(encode_n_1(10, params), params)); + static_assert(100 == decode_n_1(encode_n_1(100, params), params)); + static_assert(20 == decode_n_1(encode_n_1(20, params), params)); + static_assert(200 == decode_n_1(encode_n_1(200, params), params)); + static_assert(117 == decode_n_1(encode_n_1(117, params), params)); + static_assert(42 == decode_n_1(encode_n_1(42, params), params)); + }; + + namespace eliminated_002 { + constexpr encode_n_parameters params{.spin = 7, .clash = 97, .maim = 154}; + static_assert(0 == decode_n_1(encode_n_1(0, params), params)); + static_assert(10 == decode_n_1(encode_n_1(10, params), params)); + static_assert(100 == decode_n_1(encode_n_1(100, params), params)); + static_assert(20 == decode_n_1(encode_n_1(20, params), params)); + static_assert(200 == decode_n_1(encode_n_1(200, params), params)); + static_assert(117 == decode_n_1(encode_n_1(117, params), params)); + static_assert(42 == decode_n_1(encode_n_1(42, params), params)); + }; +} + +namespace scripting { + struct fn_string_to_binary final : public scripting::function_impl { + int32_t array_size_limit; + explicit fn_string_to_binary(int32_t _array_size_limit) + : array_size_limit(_array_size_limit) + {} + + std::optional apply(UserScript* self, std::vector n, std::optional& error) final { + if(n.size() != 1) { + error = script_error{ + .message = "/string_to_binary takes exactly 1 argument of type string" + }; + return std::nullopt; + } + + auto& arg = n.front(); + script_value target; + + if(std::holds_alternative(arg)) { + target = std::get(arg); + } else { + target = self->resolve(std::get(arg).name); + } + + if(not std::holds_alternative(target)) { + error = script_error{ + .message = "/string_to_binary takes exactly 1 argument of type string" + }; + return std::nullopt; + } + + auto& str = std::get(target); + + array result; + + std::transform(str.begin(), str.end(), std::back_inserter(result.value), [&](char value) -> script_value { + return (int32_t)value; + }); + + return result; + } + + ~fn_string_to_binary() final = default; + }; + + struct fn_binary_to_string final : public scripting::function_impl { + int32_t string_size_limit; + explicit fn_binary_to_string(int32_t _string_size_limit) + : string_size_limit(_string_size_limit) {} + + std::optional apply(UserScript* self, std::vector n, std::optional& error) final { + if(n.size() != 1) { + error = script_error{ + .message = "/binary_to_string takes exactly 1 argument of type array" + }; + return std::nullopt; + } + + auto& arg = n.front(); + script_value target; + + if(std::holds_alternative(arg)) { + target = std::get(arg); + } else { + target = self->resolve(std::get(arg).name); + } + + if(not std::holds_alternative(target)) { + error = script_error{ + .message = "/binary_to_string takes exactly 1 argument of type array" + }; + return std::nullopt; + } + + auto& ary = std::get(target); + + if(ary.value.size() > string_size_limit) { + error = script_error{ + .message = "/binary_to_string: array is too bit to fit string type" + }; + return std::nullopt; + } + + std::string result; + + const bool valid = std::any_of(ary.value.begin(), ary.value.end(), [](auto val) { + return std::visit( + wizardry::overloaded{ + [](int32_t v) { return 0 <= v && v <= 255; }, + [](auto v) { return false; } + }, + val + ); + }); + + if(not valid) { + error = script_error{ + .message = "/binary_to_string takes exactly 1 argument of type array of which contents must be numbers between 0 and 255 included" + }; + return std::nullopt; + } + + constexpr auto byte_convert = [](int32_t v) -> char { + const uint32_t v2 = v; + const uint8_t v3 = v2 & 0b1111'1111; + return char(v3); + }; + + std::transform(ary.value.begin(), ary.value.end(), std::back_inserter(result), [&](const script_value& value) -> char { + return std::visit( + wizardry::overloaded{ + byte_convert, + [](auto v) { return '\0'; } + }, + value + ); + }); + + return result; + } + + ~fn_binary_to_string() final = default; + }; + + struct fn_encode_n final : public scripting::function_impl { + int32_t string_size_limit; + explicit fn_encode_n(int32_t _string_size_limit) + : string_size_limit(_string_size_limit) {} + + std::optional apply(UserScript* self, std::vector n, std::optional& error) final { + using verifier = Verify< + details::SizeEquals<4>, + details::TypeVerifier<0, int32_t>, + details::TypeVerifier<1, int32_t>, + details::TypeVerifier<2, int32_t>, + details::OctetArrayVerifier<3> + >; + + if(not verifier{}.verify(self, n)) { + error = script_error{ + .message = "/encode_n takes exactly 3 arguments of type integer and an argument of type array" + }; + return std::nullopt; + } + + auto& arg = n.front(); + script_value target; + + if(std::holds_alternative(arg)) { + target = std::get(arg); + } else { + target = self->resolve(std::get(arg).name); + } + + bad_cryptography::encode_n_parameters params; + + { + auto& arg = n[3]; + script_value target; + + if (std::holds_alternative(arg)) { + target = std::get(arg); + } else { + target = self->resolve(std::get(arg).name); + } + + params.spin = std::get(target); + } + + { + auto& arg = n[2]; + script_value target; + + if (std::holds_alternative(arg)) { + target = std::get(arg); + } else { + target = self->resolve(std::get(arg).name); + } + + params.clash = std::get(target); + } + + { + auto& arg = n[1]; + script_value target; + + if (std::holds_alternative(arg)) { + target = std::get(arg); + } else { + target = self->resolve(std::get(arg).name); + } + + params.maim = std::get(target); + } + + auto ary = std::get(target); + + if(ary.value.size() > string_size_limit) { + error = script_error{ + .message = "/encode_n: array is too bit to fit string type" + }; + return std::nullopt; + } + + std::transform(ary.value.begin(), ary.value.end(), ary.value.begin(), [&](const script_value& value) { + return std::visit( + wizardry::overloaded{ + [&](int32_t v) -> script_value { return script_value{bad_cryptography::encode_n_1(v, params)};}, + [](auto v) -> script_value { return null{}; } + }, + value + ); + }); + + return ary; + } + + ~fn_encode_n() final = default; + }; + + interpreter register_crypto_lib(interpreter target, int32_t array_size_limit, int32_t string_size_limit) { + target->registerFunction("string_to_binary", std::make_unique(array_size_limit)); + target->registerFunction("binary_to_string", std::make_unique(string_size_limit)); + target->registerFunction("encode_n", std::make_unique(string_size_limit)); + return std::move(target); + } +} \ No newline at end of file