Unverified Commit bf7a6f38 authored by Stan Kladko's avatar Stan Kladko Committed by GitHub

Merge pull request #256 from skalenetwork/bug/SKALE-3662-mining-stuck

Bug/skale 3662 mining stuck
parents fcd33724 e00c61b3
...@@ -21,17 +21,3 @@ jobs: ...@@ -21,17 +21,3 @@ jobs:
run: git submodule update --init --recursive run: git submodule update --init --recursive
- name: Build the Docker image - name: Build the Docker image
run: docker build . --file DockerfileBase --tag skalenetwork/sgxwallet_base:latest && docker push skalenetwork/sgxwallet_base:latest run: docker build . --file DockerfileBase --tag skalenetwork/sgxwallet_base:latest && docker push skalenetwork/sgxwallet_base:latest
- name: deploy docker image
if: contains(github.ref, 'develop') || contains(github.ref, 'beta') || contains(github.ref, 'master') || contains(github.ref, 'stable')
run : |
export BRANCH=${GITHUB_REF##*/}
echo "Branch $BRANCH"
export VERSION=$(cat VERSION)
echo "Version $VERSION"
export VERSION=$(bash ./scripts/calculate_version.sh $BRANCH $VERSION)
echo "::set-env name=VERSION::$VERSION"
echo "Version $VERSION"
export RELEASE=true
echo "::set-env name=RELEASE::$RELEASE"
bash ./scripts/build_image.sh DockerfileBase sgxwallet_base
bash ./scripts/publish_image.sh sgxwallet_base
...@@ -49,4 +49,6 @@ ...@@ -49,4 +49,6 @@
/secure_enclave/*.o /secure_enclave/*.o
/*.user /*.user
/cert/*.csr /cert/*.csr
/cert/*.crt /cert/*.crt
\ No newline at end of file /jsonrpc/
/sgx-gmp/
...@@ -16,3 +16,12 @@ ...@@ -16,3 +16,12 @@
[submodule "sgx-software-enable"] [submodule "sgx-software-enable"]
path = sgx-software-enable path = sgx-software-enable
url = https://github.com/intel/sgx-software-enable url = https://github.com/intel/sgx-software-enable
[submodule "libzmq"]
path = libzmq
url = https://github.com/zeromq/libzmq.git
[submodule "cppzmq"]
path = cppzmq
url = https://github.com/zeromq/cppzmq.git
[submodule "rapidjson"]
path = rapidjson
url = https://github.com/Tencent/rapidjson.git
//
// Created by kladko on 15.12.20.
//
#include "BLSSignReqMessage.h"
#include "SGXWalletServer.hpp"
Json::Value BLSSignReqMessage::process() {
auto keyName = getStringRapid("kn");
auto hash = getStringRapid("mh");
auto t = getUint64Rapid("t");
auto n = getUint64Rapid("n");
return SGXWalletServer::blsSignMessageHashImpl(keyName, hash, t, n);
}
\ No newline at end of file
/*
Copyright (C) 2018-2019 SKALE Labs
This file is part of libBLS.
libBLS is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libBLS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with libBLS. If not, see <https://www.gnu.org/licenses/>.
@file BLSReqSignMessage.h
@author Stan Kladko
@date 2020
*/
#ifndef SGXWALLET_BLSSIGNREQMSG_H
#define SGXWALLET_BLSSIGNREQMSG_H
#include "ZMQMessage.h"
class BLSSignReqMessage : public ZMQMessage {
public:
BLSSignReqMessage(shared_ptr<rapidjson::Document>& _d) : ZMQMessage(_d) {};
virtual Json::Value process();
};
#endif //SGXWALLET_BLSSIGNREQMSG_H
//
// Created by kladko on 15.12.20.
//
#include "SGXWalletServer.hpp"
#include "ECDSASignReqMessage.h"
Json::Value ECDSASignReqMessage::process() {
auto base = getUint64Rapid("bs");
auto keyName = getStringRapid("kn");
auto hash = getStringRapid("mh");
return SGXWalletServer::ecdsaSignMessageHashImpl(base, keyName, hash);
}
\ No newline at end of file
/*
Copyright (C) 2018-2019 SKALE Labs
This file is part of libBLS.
libBLS is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libBLS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with libBLS. If not, see <https://www.gnu.org/licenses/>.
@file ECDSAReqSignMessage.h
@author Stan Kladko
@date 2020
*/
#ifndef SGXWALLET_ECDSASIGNREQMESSAGE_H
#define SGXWALLET_ECDSASIGNREQMESSAGE_H
#include "ZMQMessage.h"
class ECDSASignReqMessage : public ZMQMessage {
public:
ECDSASignReqMessage(shared_ptr <rapidjson::Document> &_d) : ZMQMessage(_d) {};
virtual Json::Value process();
};
#endif //SGXWALLET_ECDSASIGNREQMESSAGE_H
...@@ -95,22 +95,24 @@ string(__FUNCTION__) + ": server error. Please see server log."; ...@@ -95,22 +95,24 @@ string(__FUNCTION__) + ": server error. Please see server log.";
#define HANDLE_SGX_EXCEPTION(__RESULT__) \ #define HANDLE_SGX_EXCEPTION(__RESULT__) \
catch (const SGXException& _e) { \ catch (const SGXException& _e) { \
if (_e.getStatus() != 0) {__RESULT__["status"] = _e.getStatus();} else { __RESULT__["status"] = -1 * (10000 + __LINE__);}; \ if (_e.getStatus() != 0) {__RESULT__["status"] = _e.getStatus();} else { __RESULT__["status"] = -1 * (10000 + __LINE__);}; \
__RESULT__["errorMessage"] = _e.getErrString(); \ auto errStr = __FUNCTION__ + string(" failed:") + _e.getErrString(); \
if (_e.getErrString().size() == 0) {__RESULT__["errorMessage"] = string(__FUNCTION__);}; \ __RESULT__["errorMessage"] = errStr; \
spdlog::error("JSON call failed {}", __FUNCTION__); \ spdlog::error(errStr); \
return __RESULT__; \ return __RESULT__; \
} catch (const exception& _e) { \ } catch (const exception& _e) { \
__RESULT__["status"] = -1 * (10000 + __LINE__); \ __RESULT__["status"] = -1 * (10000 + __LINE__); \
exception_ptr p = current_exception(); \ exception_ptr p = current_exception(); \
__RESULT__["errorMessage"] = string(p.__cxa_exception_type()->name()) + ":" + _e.what(); \ auto errStr = __FUNCTION__ + string(" failed:") + p.__cxa_exception_type()->name() + ":" + _e.what(); \
spdlog::error("JSON call failed {}", __FUNCTION__); \ __RESULT__["errorMessage"] = errStr; \
spdlog::error(errStr); \
return __RESULT__; \ return __RESULT__; \
}\ } \
catch (...) { \ catch (...) { \
exception_ptr p = current_exception(); \ exception_ptr p = current_exception(); \
spdlog::error(string("Exception:") + p.__cxa_exception_type()->name()); \ auto errStr = __FUNCTION__ + string(" failed:") + p.__cxa_exception_type()->name(); \
__RESULT__["errorMessage"] = string(p.__cxa_exception_type()->name()); \ spdlog::error(errStr); \
spdlog::error("JSON call failed {}", __FUNCTION__); \ __RESULT__["errorMessage"] = errStr ; \
spdlog::error(errStr); \
return __RESULT__; \ return __RESULT__; \
} }
......
...@@ -49,7 +49,10 @@ AM_CFLAGS = -DUSER_SPACE -g -Og -rdynamic -Wl,--no-as-needed -lSegFault -DSGXWAL ...@@ -49,7 +49,10 @@ AM_CFLAGS = -DUSER_SPACE -g -Og -rdynamic -Wl,--no-as-needed -lSegFault -DSGXWAL
AM_CXXFLAGS = ${AM_CPPFLAGS} -rdynamic -Wl,--no-as-needed -lSegFault -DSGXWALLET_VERSION="$(WALLET_VERSION)" AM_CXXFLAGS = ${AM_CPPFLAGS} -rdynamic -Wl,--no-as-needed -lSegFault -DSGXWALLET_VERSION="$(WALLET_VERSION)"
AM_CPPFLAGS += -DSGXWALLET_VERSION="$(WALLET_VERSION)" -Wall -DSKALE_SGX=1 -DBINARY_OUTPUT=1 -Ileveldb/include -IlibBLS/bls -IlibBLS/libff -IlibBLS -fno-builtin-memset $(GMP_CPPFLAGS) -I. -I./libBLS/deps/deps_inst/x86_or_x64/include AM_CPPFLAGS += -DSGXWALLET_VERSION="$(WALLET_VERSION)" -Wall -DSKALE_SGX=1 -DBINARY_OUTPUT=1 -Ileveldb/include -IlibBLS/bls\
-IlibBLS/libff -IlibBLS -fno-builtin-memset $(GMP_CPPFLAGS) -I. \
-I./libBLS/deps/deps_inst/x86_or_x64/include -I./libzmq/include -I./cppzmq -I./third_party/zguide \
-I./rapidjson/include/rapidjson
## Additional targets to remove with 'make clean'. You must list ## Additional targets to remove with 'make clean'. You must list
## any edger8r generated files here. ## any edger8r generated files here.
...@@ -66,12 +69,12 @@ bin_PROGRAMS = sgxwallet testw sgx_util ...@@ -66,12 +69,12 @@ bin_PROGRAMS = sgxwallet testw sgx_util
## have to be explicitly listed. ## have to be explicitly listed.
## have to be explicitly listed ## have to be explicitly listed
COMMON_SRC = InvalidStateException.cpp Exception.cpp InvalidArgumentException.cpp Log.cpp \
SGXWalletServer.cpp SGXRegistrationServer.cpp CSRManagerServer.cpp SGXInfoServer.cpp \ COMMON_SRC = ECDSASignReqMessage.cpp BLSSignReqMessage.cpp ZMQMessage.cpp ZMQServer.cpp ServerWorker.cpp InvalidStateException.cpp Exception.cpp InvalidArgumentException.cpp Log.cpp \
BLSCrypto.cpp ECDSACrypto.cpp \ SGXWalletServer.cpp SGXRegistrationServer.cpp CSRManagerServer.cpp BLSCrypto.cpp \
DKGCrypto.cpp ServerInit.cpp BLSPrivateKeyShareSGX.cpp LevelDB.cpp ServerDataChecker.cpp SEKManager.cpp \ DKGCrypto.cpp ServerInit.cpp BLSPrivateKeyShareSGX.cpp LevelDB.cpp ServerDataChecker.cpp SEKManager.cpp \
third_party/intel/sgx_stub.c third_party/intel/sgx_detect_linux.c third_party/intel/create_enclave.c third_party/intel/oc_alloc.c \ third_party/intel/sgx_stub.c third_party/intel/sgx_detect_linux.c third_party/intel/create_enclave.c third_party/intel/oc_alloc.c \
ECDSAImpl.c TestUtils.cpp sgxwallet.c ECDSAImpl.c TestUtils.cpp sgxwallet.c SGXInfoServer.cpp ECDSACrypto.cpp
COMMON_ENCLAVE_SRC = secure_enclave_u.c secure_enclave_u.h COMMON_ENCLAVE_SRC = secure_enclave_u.c secure_enclave_u.h
sgxwallet_SOURCES = sgxwall.cpp $(COMMON_SRC) sgxwallet_SOURCES = sgxwall.cpp $(COMMON_SRC)
...@@ -100,8 +103,10 @@ secure_enclave.signed.so: secure_enclave/secure_enclave.signed.so ...@@ -100,8 +103,10 @@ secure_enclave.signed.so: secure_enclave/secure_enclave.signed.so
sgxwallet_LDADD=-l$(SGX_URTS_LIB) -l$(SGX_UAE_SERVICE_LIB) -LlibBLS/deps/deps_inst/x86_or_x64/lib -Lleveldb/build -LlibBLS/build \ sgxwallet_LDADD=-l$(SGX_URTS_LIB) -l$(SGX_UAE_SERVICE_LIB) -LlibBLS/deps/deps_inst/x86_or_x64/lib -Lleveldb/build -LlibBLS/build \
-LlibBLS/build/libff/libff \ -LlibBLS/build/libff/libff \
-Llibzmq/build/lib/ \
-l:libbls.a -l:libleveldb.a \ -l:libbls.a -l:libleveldb.a \
-l:libff.a -lgmp -ldl -l:libsgx_capable.a -l:libsgx_tprotected_fs.a \ -l:libff.a -lgmp -ldl -l:libsgx_capable.a -l:libsgx_tprotected_fs.a \
-l:libzmq.a \
-ljsonrpccpp-stub -ljsonrpccpp-server -ljsonrpccpp-client -ljsonrpccpp-common -ljsoncpp -lmicrohttpd \ -ljsonrpccpp-stub -ljsonrpccpp-server -ljsonrpccpp-client -ljsonrpccpp-common -ljsoncpp -lmicrohttpd \
-lboost_system -lboost_thread -lgnutls -lgcrypt -lidn2 -lcurl -lssl -lcrypto -lz -lpthread -lstdc++fs -lboost_system -lboost_thread -lgnutls -lgcrypt -lidn2 -lcurl -lssl -lcrypto -lz -lpthread -lstdc++fs
...@@ -111,10 +116,12 @@ nodist_testw_SOURCES=${nodist_sgxwallet_SOURCES} ...@@ -111,10 +116,12 @@ nodist_testw_SOURCES=${nodist_sgxwallet_SOURCES}
EXTRA_testw_DEPENDENCIES=${EXTRA_sgxwallet_DEPENDENCIES} EXTRA_testw_DEPENDENCIES=${EXTRA_sgxwallet_DEPENDENCIES}
testw_LDADD= ${sgxwallet_LDADD} testw_LDADD= ${sgxwallet_LDADD}
sgx_util_SOURCES= InvalidStateException.cpp Exception.cpp InvalidArgumentException.cpp Log.cpp sgx_util.cpp stubclient.cpp LevelDB.cpp \ sgx_util_SOURCES= InvalidStateException.cpp Exception.cpp InvalidArgumentException.cpp Log.cpp sgx_util.cpp stubclient.cpp LevelDB.cpp SGXRegistrationServer.cpp CSRManagerServer.cpp
SGXRegistrationServer.cpp CSRManagerServer.cpp SGXInfoServer.cpp
sgx_util_LDADD=-LlibBLS/deps/deps_inst/x86_or_x64/lib -Lleveldb/build -LlibBLS/build \
-LlibBLS/build/libff/libff \
-Llibzmq/build/lib/ \
-l:libzmq.a \
-l:libbls.a -l:libleveldb.a \
-l:libff.a -lgmp -ljsonrpccpp-stub -ljsonrpccpp-server -ljsonrpccpp-client -ljsonrpccpp-common -ljsoncpp -lmicrohttpd -lgnutls -lgcrypt -lidn2 -lcurl -lssl -lcrypto -lz -lpthread -ldl
sgx_util_LDADD= -LlibBLS/deps/deps_inst/x86_or_x64/lib -Lleveldb/build -LlibBLS/build \
-LlibBLS/build/libff/libff \
-l:libbls.a -l:libleveldb.a \
-l:libff.a -lgmp -ljsonrpccpp-stub -ljsonrpccpp-server -ljsonrpccpp-client -ljsonrpccpp-common -ljsoncpp -lmicrohttpd -lgnutls -lgcrypt -lidn2 -lcurl -lssl -lcrypto -lz -lpthread -ldl
...@@ -25,8 +25,11 @@ ...@@ -25,8 +25,11 @@
#include "abstractstubserver.h" #include "abstractstubserver.h"
#include <jsonrpccpp/server/connectors/httpserver.h> #include <jsonrpccpp/server/connectors/httpserver.h>
#include <stdio.h> #include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <unistd.h>
#include "sgxwallet_common.h" #include "sgxwallet_common.h"
#include "sgxwallet.h" #include "sgxwallet.h"
...@@ -42,13 +45,6 @@ ...@@ -42,13 +45,6 @@
#include "SGXWalletServer.hpp" #include "SGXWalletServer.hpp"
#include "ServerDataChecker.h" #include "ServerDataChecker.h"
#include <algorithm>
#include <stdlib.h>
#include <unistd.h>
#include "ServerInit.h" #include "ServerInit.h"
#include "Log.h" #include "Log.h"
...@@ -307,6 +303,8 @@ SGXWalletServer::blsSignMessageHashImpl(const string &_keyShareName, const strin ...@@ -307,6 +303,8 @@ SGXWalletServer::blsSignMessageHashImpl(const string &_keyShareName, const strin
result["signatureShare"] = string(signature.data()); result["signatureShare"] = string(signature.data());
RETURN_SUCCESS(result); RETURN_SUCCESS(result);
} }
......
...@@ -25,7 +25,8 @@ ...@@ -25,7 +25,8 @@
#define SGXWALLET_SGXWALLETSERVER_HPP #define SGXWALLET_SGXWALLETSERVER_HPP
#include "mutex"
#include "memory"
#include <jsonrpccpp/server/connectors/httpserver.h> #include <jsonrpccpp/server/connectors/httpserver.h>
......
...@@ -58,6 +58,7 @@ ...@@ -58,6 +58,7 @@
#include "BLSCrypto.h" #include "BLSCrypto.h"
#include "ServerInit.h" #include "ServerInit.h"
#include "SGXException.h" #include "SGXException.h"
#include "ZMQServer.h"
#include "SGXWalletServer.hpp" #include "SGXWalletServer.hpp"
uint32_t enclaveLogLevel = 0; uint32_t enclaveLogLevel = 0;
...@@ -88,6 +89,9 @@ void systemHealthCheck() { ...@@ -88,6 +89,9 @@ void systemHealthCheck() {
} }
} }
static ZMQServer* zmqServer = nullptr;
atomic<bool> exiting(false);
void initUserSpace() { void initUserSpace() {
libff::inhibit_profiling_counters = true; libff::inhibit_profiling_counters = true;
...@@ -100,7 +104,25 @@ void initUserSpace() { ...@@ -100,7 +104,25 @@ void initUserSpace() {
systemHealthCheck(); systemHealthCheck();
#endif #endif
#ifdef EXPERIMENTAL_ZMQ_SERVER
zmqServer = new ZMQServer();
static std::thread serverThread(std::bind(&ZMQServer::run, zmqServer));
#endif
}
void exitZMQServer() {
#ifdef EXPERIMENTAL_ZMQ_SERVER
auto doExit = !exiting.exchange(true);
if (doExit) {
spdlog::info("Exiting zmq server ...");
delete zmqServer;
spdlog::info("Exited zmq server ...");
zmqServer = nullptr;
}
#endif
} }
uint64_t initEnclave() { uint64_t initEnclave() {
...@@ -162,9 +184,9 @@ uint64_t initEnclave() { ...@@ -162,9 +184,9 @@ uint64_t initEnclave() {
void initAll(uint32_t _logLevel, bool _checkCert, bool _autoSign, bool _generateTestKeys) { void initAll(uint32_t _logLevel, bool _checkCert, bool _autoSign, bool _generateTestKeys) {
static atomic<bool> sgxServerInited(false); static atomic<bool> sgxServerInited(false);
static mutex initMutex; static mutex initMutex;
enclaveLogLevel = _logLevel; enclaveLogLevel = _logLevel;
......
...@@ -38,6 +38,8 @@ EXTERNC void initUserSpace(); ...@@ -38,6 +38,8 @@ EXTERNC void initUserSpace();
EXTERNC uint64_t initEnclave(); EXTERNC uint64_t initEnclave();
EXTERNC void exitZMQServer();
#endif //SGXWALLET_SERVERINIT_H #endif //SGXWALLET_SERVERINIT_H
//
// Created by kladko on 14.12.20.
//
#include "common.h"
#include <json/writer.h>
#include "ZMQMessage.h"
#include "ServerWorker.h"
ServerWorker::ServerWorker(zmq::context_t &ctx, int sock_type) : ctx_(ctx),
worker_(ctx_, sock_type) {};
void ServerWorker::work() {
worker_.connect("inproc://backend");
try {
while (true) {
zmq::message_t msg;
zmq::message_t copied_msg;
worker_.recv(&msg);
vector<uint8_t> msgData(msg.size() + 1, 0);
memcpy(msgData.data(), msg.data(), msg.size());
auto parsedMsg = ZMQMessage::parse(msgData);
CHECK_STATE(parsedMsg);
auto reply = parsedMsg->process();
Json::FastWriter fastWriter;
std::string replyStr = fastWriter.write(reply);
zmq::message_t replyMsg(replyStr.c_str(),replyStr.size() + 1);
worker_.send(replyMsg);
}
}
catch (std::exception &e) {
spdlog::info("Exiting zmq server worker:{}", e.what());
return;
} catch (...) {
spdlog::error("Error in zmq server worker");
return;
}
}
//
// Created by kladko on 14.12.20.
//
#ifndef SGXWALLET_SERVERWORKER_H
#define SGXWALLET_SERVERWORKER_H
#include <vector>
#include <thread>
#include <memory>
#include <functional>
#include "abstractstubserver.h"
#include <zmq.hpp>
#include "zhelpers.hpp"
#include "third_party/spdlog/spdlog.h"
#include "document.h"
class ServerWorker {
public:
ServerWorker(zmq::context_t &ctx, int sock_type );
void work();
private:
zmq::context_t &ctx_;
zmq::socket_t worker_;
};
#endif //SGXWALLET_SERVERWORKER_H
...@@ -21,10 +21,8 @@ ...@@ -21,10 +21,8 @@
@date 2020 @date 2020
*/ */
#include <dkg/dkg.h>
#include <jsonrpccpp/server/connectors/httpserver.h> #include <jsonrpccpp/server/connectors/httpserver.h>
#include <libff/algebra/curves/alt_bn128/alt_bn128_pp.hpp>
#include <dkg/dkg.h>
#include "sgxwallet_common.h" #include "sgxwallet_common.h"
#include "third_party/intel/create_enclave.h" #include "third_party/intel/create_enclave.h"
#include "secure_enclave_u.h" #include "secure_enclave_u.h"
......
/*
Copyright (C) 2020 SKALE Labs
This file is part of skale-consensus.
skale-consensus is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
skale-consensus is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with skale-consensus. If not, see <https://www.gnu.org/licenses/>.
@file ZMQMessage.cpp
@author Stan Kladko
@date 2020
*/
#include "common.h"
#include "BLSSignReqMessage.h"
#include "ECDSASignReqMessage.h"
#include "ZMQMessage.h"
uint64_t ZMQMessage::getUint64Rapid(const char *_name) {
CHECK_STATE(_name);
CHECK_STATE(d->HasMember(_name));
const rapidjson::Value& a = (*d)[_name];
CHECK_STATE(a.IsUint64());
return a.GetUint64();
};
string ZMQMessage::getStringRapid(const char *_name) {
CHECK_STATE(_name);
CHECK_STATE(d->HasMember(_name));
CHECK_STATE((*d)[_name].IsString());
return (*d)[_name].GetString();
};
shared_ptr<ZMQMessage> ZMQMessage::parse(vector<uint8_t>& _msg) {
CHECK_STATE(_msg.at(_msg.size() - 1) == 0);
auto d = make_shared<rapidjson::Document>();
d->Parse((const char*) _msg.data());
CHECK_STATE(!d->HasParseError());
CHECK_STATE(d->IsObject())
CHECK_STATE(d->HasMember("type"));
CHECK_STATE((*d)["type"].IsString());
auto type = (*d)["type"].GetString();
shared_ptr<ZMQMessage> result;
if (type == ZMQMessage::BLS_SIGN_REQ) {
result = make_shared<BLSSignReqMessage>(d);
} else if (type == ZMQMessage::ECDSA_SIGN_REQ) {
result = make_shared<ECDSASignReqMessage>(d);
} else {
throw SGXException(-301, "Incorrect zmq message type: " + string(type));
}
return result;
}
/*
Copyright (C) 2018-2019 SKALE Labs
This file is part of skale-consensus.
skale-consensus is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
skale-consensus is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with skale-consensus. If not, see <https://www.gnu.org/licenses/>.
@file ZMQMessage.h
@author Stan Kladko
@date 2018
*/
#pragma once
#include <memory>
#include <vector>
#include "abstractstubserver.h"
#include "document.h"
#include "SGXException.h"
using namespace std;
class ZMQMessage {
shared_ptr<rapidjson::Document> d;
static constexpr const char *BLS_SIGN_REQ = "BLSSignReq";
static constexpr const char *BLS_SIGN_RSP = "BLSSignRsp";
static constexpr const char *ECDSA_SIGN_REQ = "ECDSASignReq";
static constexpr const char *ECDSA_SIGN_RSP = "ECDSASignRsp";
protected:
public:
explicit ZMQMessage(shared_ptr<rapidjson::Document> &_d) : d(_d) {
};
string getStringRapid(const char *_name);
uint64_t getUint64Rapid(const char *_name);
static shared_ptr<ZMQMessage> parse(vector<uint8_t> &_msg);
virtual Json::Value process() = 0;
};
\ No newline at end of file
/*
Copyright (C) 2019-Present SKALE Labs
This file is part of sgxwallet.
sgxwallet is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
sgxwallet is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with sgxwallet. If not, see <https://www.gnu.org/licenses/>.
@file ZMQServer.cpp
@author Stan Kladko
@date 2019
*/
#include "third_party/spdlog/spdlog.h"
#include "ZMQServer.h"
#include "sgxwallet_common.h"
using namespace std;
ZMQServer::ZMQServer()
: isExitRequested(false), ctx_(1),
frontend_(ctx_, ZMQ_ROUTER),
backend_(ctx_, ZMQ_DEALER) {}
void ZMQServer::run() {
auto port = BASE_PORT + 4;
spdlog::info("Starting zmq server ...");
try {
frontend_.bind("tcp://*:" + to_string(BASE_PORT + 4));
} catch (...) {
spdlog::error("Server task could not bind to port:{}", port);
exit(-100);
}
spdlog::info("Bound port ...");
try {
backend_.bind("inproc://backend");
} catch (exception &e) {
spdlog::error("Could not bind to zmq backend: {}", e.what());
exit(-101);
}
spdlog::info("Creating {} zmq server workers ...", kMaxThread);
try {
for (int i = 0; i < kMaxThread; ++i) {
worker.push_back(new ServerWorker(ctx_, ZMQ_DEALER));
worker_thread.push_back(new std::thread(std::bind(&ServerWorker::work, worker[i])));
}
} catch (std::exception &e) {
spdlog::error("Could not create zmq server workers:{} ", e.what());
exit(-102);
}
try {
zmq::proxy(static_cast<void *>(frontend_), static_cast<void *>(backend_), nullptr);
} catch (exception& _e) {
spdlog::info("Exiting zmq server {}", _e.what());
return;
} catch (...) {
spdlog::info("Exiting zmq server");
return;
}
}
/*
Copyright (C) 2019-Present SKALE Labs
This file is part of sgxwallet.
sgxwallet is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
sgxwallet is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with sgxwallet. If not, see <https://www.gnu.org/licenses/>.
@file ZMQServer.h
@author Stan Kladko
@date 2020
*/
#ifndef SGXWALLET_ZMQServer_H
#define SGXWALLET_ZMQServer_H
#include <vector>
#include <thread>
#include <memory>
#include <functional>
#include <atomic>
#include <zmq.hpp>
#include "zhelpers.hpp"
#include "ServerWorker.h"
using namespace std;
class ZMQServer {
public:
ZMQServer();
atomic<bool> isExitRequested;
enum {
kMaxThread = 5
};
void run();
private:
zmq::context_t ctx_;
zmq::socket_t frontend_;
zmq::socket_t backend_;
std::vector<ServerWorker *> worker;
std::vector<std::thread *> worker_thread;
};
#endif //SGXWALLET_ZMQServer_H
...@@ -31,12 +31,14 @@ using namespace std; ...@@ -31,12 +31,14 @@ using namespace std;
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <memory> #include <memory>
#include <vector>
#include <boost/throw_exception.hpp> #include <boost/throw_exception.hpp>
#include <gmp.h> #include <gmp.h>
#include "secure_enclave/Verify.h" #include "secure_enclave/Verify.h"
#include "InvalidStateException.h" #include "InvalidStateException.h"
#include "SGXException.h"
#define SAFE_FREE(__POINTER__) {if (__POINTER__) {free(__POINTER__); __POINTER__ = NULL;}} #define SAFE_FREE(__POINTER__) {if (__POINTER__) {free(__POINTER__); __POINTER__ = NULL;}}
...@@ -72,7 +74,7 @@ inline void print_stack() { ...@@ -72,7 +74,7 @@ inline void print_stack() {
if (!(_EXPRESSION_)) { \ if (!(_EXPRESSION_)) { \
auto __msg__ = std::string("State check failed::") + #_EXPRESSION_ + " " + std::string(__FILE__) + ":" + std::to_string(__LINE__); \ auto __msg__ = std::string("State check failed::") + #_EXPRESSION_ + " " + std::string(__FILE__) + ":" + std::to_string(__LINE__); \
print_stack(); \ print_stack(); \
throw InvalidStateException(__msg__, __CLASS_NAME__);} BOOST_THROW_EXCEPTION(SGXException(-100, string(__CLASS_NAME__) + ":" + __msg__));}
#define HANDLE_TRUSTED_FUNCTION_ERROR(__STATUS__, __ERR_STATUS__, __ERR_MSG__) \ #define HANDLE_TRUSTED_FUNCTION_ERROR(__STATUS__, __ERR_STATUS__, __ERR_MSG__) \
...@@ -81,7 +83,7 @@ string __ERR_STRING__ = string("SGX enclave call to ") + \ ...@@ -81,7 +83,7 @@ string __ERR_STRING__ = string("SGX enclave call to ") + \
__FUNCTION__ + " failed with status:" \ __FUNCTION__ + " failed with status:" \
+ to_string(__STATUS__) + \ + to_string(__STATUS__) + \
" Err message:" + __ERR_MSG__; \ " Err message:" + __ERR_MSG__; \
BOOST_THROW_EXCEPTION(runtime_error(__ERR_MSG__)); \ BOOST_THROW_EXCEPTION(SGXException(-102, string(__ERR_MSG__))); \
}\ }\
\ \
if (__ERR_STATUS__ != 0) {\ if (__ERR_STATUS__ != 0) {\
......
Subproject commit 18db4568f9ff1568193d3a496d87bed4bc9ec59e
Subproject commit 75dfbae0d1dc65312d2aaad6902911a3214f4819
Subproject commit 0ccdbf364c577803e2a751f5aededce935314313
...@@ -34,14 +34,21 @@ makeExecutable = subprocess.check_output(["which", "make"]) ...@@ -34,14 +34,21 @@ makeExecutable = subprocess.check_output(["which", "make"])
SCRIPTS_DIR = topDir + "/scripts" SCRIPTS_DIR = topDir + "/scripts"
GMP_DIR = topDir + "/sgx-gmp" GMP_DIR = topDir + "/sgx-gmp"
SGX_SDK_DIR_SSL = topDir + "/sgx-sdk-build/sgxsdk" SGX_SDK_DIR_SSL = topDir + "/sgx-sdk-build/sgxsdk"
ZMQ_DIR = topDir + "/libzmq"
ZMQ_BUILD_DIR = ZMQ_DIR + "/build"
CZMQ_DIR = topDir + "/cppzmq"
CZMQ_BUILD_DIR = CZMQ_DIR + "/build"
LEVELDB_DIR = topDir + "/leveldb" LEVELDB_DIR = topDir + "/leveldb"
LEVELDB_BUILD_DIR = LEVELDB_DIR + "/build" LEVELDB_BUILD_DIR = LEVELDB_DIR + "/build"
GMP_BUILD_DIR = topDir + "/gmp-build" GMP_BUILD_DIR = topDir + "/gmp-build"
TGMP_BUILD_DIR = topDir + "/tgmp-build" TGMP_BUILD_DIR = topDir + "/tgmp-build"
SDK_DIR = topDir + "/sgx-sdk-build" SDK_DIR = topDir + "/sgx-sdk-build"
JSON_LIBS_DIR = topDir + "/jsonrpc"
BLS_DIR = topDir + "/libBLS" BLS_DIR = topDir + "/libBLS"
BLS_BUILD_DIR = BLS_DIR + "/build" BLS_BUILD_DIR = BLS_DIR + "/build"
JSON_LIBS_DIR = topDir + "/jsonrpc"
print("Cleaning") print("Cleaning")
...@@ -58,17 +65,13 @@ subprocess.call(["rm", "-rf", TGMP_BUILD_DIR]) ...@@ -58,17 +65,13 @@ subprocess.call(["rm", "-rf", TGMP_BUILD_DIR])
subprocess.call(["rm", "-rf", SDK_DIR]) subprocess.call(["rm", "-rf", SDK_DIR])
assert subprocess.call(["cp", "configure.gmp", GMP_DIR + "/configure"]) == 0
print("Build LevelDB");
os.chdir(LEVELDB_DIR)
assert subprocess.call(["bash", "-c", "mkdir -p build"]) == 0
os.chdir(LEVELDB_BUILD_DIR)
assert subprocess.call(["bash", "-c", "cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build ."]) == 0
assert subprocess.call(["cp", "configure.gmp", GMP_DIR + "/configure"]) == 0
print("Build LibBLS"); print("Build LibBLS");
os.chdir(BLS_DIR + "/deps") os.chdir(BLS_DIR + "/deps")
...@@ -77,7 +80,23 @@ os.chdir(BLS_DIR) ...@@ -77,7 +80,23 @@ os.chdir(BLS_DIR)
assert subprocess.call(["bash", "-c", "cmake -H. -Bbuild"]) == 0 assert subprocess.call(["bash", "-c", "cmake -H. -Bbuild"]) == 0
os.chdir(BLS_DIR + "/build") os.chdir(BLS_DIR + "/build")
assert subprocess.call(["bash", "-c", "make"]) == 0 assert subprocess.call(["bash", "-c", "make"]) == 0
print("Build ZMQ");
os.chdir(ZMQ_DIR)
assert subprocess.call(["bash", "-c", "mkdir -p build"]) == 0
os.chdir(ZMQ_BUILD_DIR)
assert subprocess.call(["bash", "-c", "cmake -DDZMQ_EXPERIMENTAL=1 -DCMAKE_BUILD_TYPE=Release .. && cmake --build ."]) == 0
print("Build LevelDB");
os.chdir(LEVELDB_DIR)
assert subprocess.call(["bash", "-c", "mkdir -p build"]) == 0
os.chdir(LEVELDB_BUILD_DIR)
assert subprocess.call(["bash", "-c", "cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build ."]) == 0
print("Build JSON"); print("Build JSON");
os.chdir(JSON_LIBS_DIR) os.chdir(JSON_LIBS_DIR)
......
...@@ -34,10 +34,13 @@ ...@@ -34,10 +34,13 @@
#include "TestUtils.h" #include "TestUtils.h"
#include "ZMQServer.h"
#include "testw.h" #include "testw.h"
#include "sgxwall.h" #include "sgxwall.h"
#include "sgxwallet.h" #include "sgxwallet.h"
void SGXWallet::usage() { void SGXWallet::usage() {
cerr << "usage: sgxwallet\n"; cerr << "usage: sgxwallet\n";
exit(-21); exit(-21);
...@@ -201,6 +204,8 @@ int main(int argc, char *argv[]) { ...@@ -201,6 +204,8 @@ int main(int argc, char *argv[]) {
cerr << "Successfully completed generating test keys into sgx_data" << endl; cerr << "Successfully completed generating test keys into sgx_data" << endl;
} }
while (true) { while (true) {
sleep(10); sleep(10);
} }
......
...@@ -89,6 +89,7 @@ public: ...@@ -89,6 +89,7 @@ public:
} }
~TestFixtureHTTPS() { ~TestFixtureHTTPS() {
exitZMQServer();
TestUtils::destroyEnclave(); TestUtils::destroyEnclave();
} }
}; };
...@@ -101,6 +102,7 @@ public: ...@@ -101,6 +102,7 @@ public:
} }
~TestFixtureNoResetFromBackup() { ~TestFixtureNoResetFromBackup() {
exitZMQServer();
TestUtils::destroyEnclave(); TestUtils::destroyEnclave();
} }
}; };
...@@ -114,6 +116,7 @@ public: ...@@ -114,6 +116,7 @@ public:
} }
~TestFixtureNoReset() { ~TestFixtureNoReset() {
exitZMQServer();
TestUtils::destroyEnclave(); TestUtils::destroyEnclave();
} }
}; };
......
Copyright (c) 2010-2013 iMatix Corporation and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
#ifndef __ZHELPERS_HPP_INCLUDED__
#define __ZHELPERS_HPP_INCLUDED__
// Include a bunch of headers that we will need in the examples
#include <zmq.hpp> // https://github.com/zeromq/cppzmq
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <time.h>
#include <assert.h>
#include <stdlib.h> // random() RAND_MAX
#include <stdio.h>
#include <stdarg.h>
#include <signal.h>
#if (!defined(WIN32))
# include <sys/time.h>
# include <unistd.h>
#endif
// Bring Windows MSVC up to C99 scratch
#if (defined (WIN32))
typedef unsigned long ulong;
typedef unsigned int uint;
typedef __int64 int64_t;
#endif
// On some version of Windows, POSIX subsystem is not installed by default.
// So define srandom and random ourself.
//
#if (defined (WIN32))
# define srandom srand
# define random rand
#endif
// Visual Studio versions below 2015 do not support sprintf properly. This is a workaround.
// Taken from http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf c99_snprintf
#define vsnprintf c99_vsnprintf
inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
{
int count = -1;
if (size != 0)
count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
if (count == -1)
count = _vscprintf(format, ap);
return count;
}
inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...)
{
int count;
va_list ap;
va_start(ap, format);
count = c99_vsnprintf(outBuf, size, format, ap);
va_end(ap);
return count;
}
#endif
// Provide random number from 0..(num-1)
#define within(num) (int) ((float)((num) * random ()) / (RAND_MAX + 1.0))
// Receive 0MQ string from socket and convert into C string
// Caller must free returned string.
inline static char *
s_recv(void *socket, int flags = 0) {
zmq_msg_t message;
zmq_msg_init(&message);
int rc = zmq_msg_recv(&message, socket, flags);
if (rc < 0)
return nullptr; // Context terminated, exit
size_t size = zmq_msg_size(&message);
char *string = (char*)malloc(size + 1);
memcpy(string, zmq_msg_data(&message), size);
zmq_msg_close(&message);
string[size] = 0;
return (string);
}
// Receive 0MQ string from socket and convert into string
inline static std::string
s_recv (zmq::socket_t & socket, int flags = 0) {
zmq::message_t message;
socket.recv(&message, flags);
return std::string(static_cast<char*>(message.data()), message.size());
}
inline static bool s_recv(zmq::socket_t & socket, std::string & ostring, int flags = 0)
{
zmq::message_t message;
bool rc = socket.recv(&message, flags);
if (rc) {
ostring = std::string(static_cast<char*>(message.data()), message.size());
}
return (rc);
}
// Convert C string to 0MQ string and send to socket
inline static int
s_send(void *socket, const char *string, int flags = 0) {
int rc;
zmq_msg_t message;
zmq_msg_init_size(&message, strlen(string));
memcpy(zmq_msg_data(&message), string, strlen(string));
rc = zmq_msg_send(&message, socket, flags);
assert(-1 != rc);
zmq_msg_close(&message);
return (rc);
}
// Convert string to 0MQ string and send to socket
inline static bool
s_send (zmq::socket_t & socket, const std::string & string, int flags = 0) {
zmq::message_t message(string.size());
memcpy (message.data(), string.data(), string.size());
bool rc = socket.send (message, flags);
return (rc);
}
// Sends string as 0MQ string, as multipart non-terminal
inline static int
s_sendmore(void *socket, char *string) {
int rc;
zmq_msg_t message;
zmq_msg_init_size(&message, strlen(string));
memcpy(zmq_msg_data(&message), string, strlen(string));
//rc = zmq_send(socket, string, strlen(string), ZMQ_SNDMORE);
rc = zmq_msg_send(&message, socket, ZMQ_SNDMORE);
assert(-1 != rc);
zmq_msg_close(&message);
return (rc);
}
// Sends string as 0MQ string, as multipart non-terminal
inline static bool
s_sendmore (zmq::socket_t & socket, const std::string & string) {
zmq::message_t message(string.size());
memcpy (message.data(), string.data(), string.size());
bool rc = socket.send (message, ZMQ_SNDMORE);
return (rc);
}
// Receives all message parts from socket, prints neatly
//
inline static void
s_dump (zmq::socket_t & socket)
{
std::cout << "----------------------------------------" << std::endl;
while (1) {
// Process all parts of the message
zmq::message_t message;
socket.recv(&message);
// Dump the message as text or binary
size_t size = message.size();
std::string data(static_cast<char*>(message.data()), size);
bool is_text = true;
size_t char_nbr;
unsigned char byte;
for (char_nbr = 0; char_nbr < size; char_nbr++) {
byte = data [char_nbr];
if (byte < 32 || byte > 127)
is_text = false;
}
std::cout << "[" << std::setfill('0') << std::setw(3) << size << "]";
for (char_nbr = 0; char_nbr < size; char_nbr++) {
if (is_text)
std::cout << (char)data [char_nbr];
else
std::cout << std::setfill('0') << std::setw(2)
<< std::hex << (unsigned int) data [char_nbr];
}
std::cout << std::endl;
int more = 0; // Multipart detection
size_t more_size = sizeof (more);
socket.getsockopt (ZMQ_RCVMORE, &more, &more_size);
if (!more)
break; // Last message part
}
}
#if (!defined (WIN32))
// Set simple random printable identity on socket
// Caution:
// DO NOT call this version of s_set_id from multiple threads on MS Windows
// since s_set_id will call rand() on MS Windows. rand(), however, is not
// reentrant or thread-safe. See issue #521.
inline std::string
s_set_id (zmq::socket_t & socket)
{
std::stringstream ss;
ss << std::hex << std::uppercase
<< std::setw(4) << std::setfill('0') << within (0x10000) << "-"
<< std::setw(4) << std::setfill('0') << within (0x10000);
socket.setsockopt(ZMQ_IDENTITY, ss.str().c_str(), ss.str().length());
return ss.str();
}
#else
// Fix #521
inline std::string
s_set_id(zmq::socket_t & socket, intptr_t id)
{
std::stringstream ss;
ss << std::hex << std::uppercase
<< std::setw(4) << std::setfill('0') << id;
socket.setsockopt(ZMQ_IDENTITY, ss.str().c_str(), ss.str().length());
return ss.str();
}
#endif
// Report 0MQ version number
//
inline static void
s_version (void)
{
int major, minor, patch;
zmq_version (&major, &minor, &patch);
std::cout << "Current 0MQ version is " << major << "." << minor << "." << patch << std::endl;
}
inline static void
s_version_assert (int want_major, int want_minor)
{
int major, minor, patch;
zmq_version (&major, &minor, &patch);
if (major < want_major
|| (major == want_major && minor < want_minor)) {
std::cout << "Current 0MQ version is " << major << "." << minor << std::endl;
std::cout << "Application needs at least " << want_major << "." << want_minor
<< " - cannot continue" << std::endl;
exit (EXIT_FAILURE);
}
}
// Return current system clock as milliseconds
inline static int64_t
s_clock (void)
{
#if (defined (WIN32))
FILETIME fileTime;
GetSystemTimeAsFileTime(&fileTime);
unsigned __int64 largeInt = fileTime.dwHighDateTime;
largeInt <<= 32;
largeInt |= fileTime.dwLowDateTime;
largeInt /= 10000; // FILETIME is in units of 100 nanoseconds
return (int64_t)largeInt;
#else
struct timeval tv;
gettimeofday (&tv, NULL);
return (int64_t) (tv.tv_sec * 1000 + tv.tv_usec / 1000);
#endif
}
// Sleep for a number of milliseconds
inline static void
s_sleep (int msecs)
{
#if (defined (WIN32))
Sleep (msecs);
#else
struct timespec t;
t.tv_sec = msecs / 1000;
t.tv_nsec = (msecs % 1000) * 1000000;
nanosleep (&t, NULL);
#endif
}
inline static void
s_console (const char *format, ...)
{
time_t curtime = time (NULL);
struct tm *loctime = localtime (&curtime);
char *formatted = new char[20];
strftime (formatted, 20, "%y-%m-%d %H:%M:%S ", loctime);
printf ("%s", formatted);
delete[] formatted;
va_list argptr;
va_start (argptr, format);
vprintf (format, argptr);
va_end (argptr);
printf ("\n");
}
// ---------------------------------------------------------------------
// Signal handling
//
// Call s_catch_signals() in your application at startup, and then exit
// your main loop if s_interrupted is ever 1. Works especially well with
// zmq_poll.
static int s_interrupted = 0;
inline static void s_signal_handler (int signal_value)
{
s_interrupted = 1;
}
inline static void s_catch_signals ()
{
#if (!defined(WIN32))
struct sigaction action;
action.sa_handler = s_signal_handler;
action.sa_flags = 0;
sigemptyset (&action.sa_mask);
sigaction (SIGINT, &action, NULL);
sigaction (SIGTERM, &action, NULL);
#endif
}
#endif
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment