diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2d8747c6..be7e9bde 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,3 @@ * @olehnikolaiev @kladkogex *.md @skalenetwork/docowners +/docs/ @skalenetwork/docowners diff --git a/BLSCrypto.cpp b/BLSCrypto.cpp index dbfbc59f..9a31ca9b 100644 --- a/BLSCrypto.cpp +++ b/BLSCrypto.cpp @@ -46,6 +46,7 @@ #include "LevelDB.h" #include "ServerInit.h" #include "BLSCrypto.h" +#include "CryptoTools.h" string *FqToString(libff::alt_bn128_Fq *_fq) { @@ -65,71 +66,6 @@ string *FqToString(libff::alt_bn128_Fq *_fq) { return new string(arr); } -int char2int(char _input) { - if (_input >= '0' && _input <= '9') - return _input - '0'; - if (_input >= 'A' && _input <= 'F') - return _input - 'A' + 10; - if (_input >= 'a' && _input <= 'f') - return _input - 'a' + 10; - return -1; -} - -vector carray2Hex(const unsigned char *d, uint64_t _len) { - - CHECK_STATE(d); - - vector _hexArray( 2 * _len + 1); - - char hexval[16] = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - - for (uint64_t j = 0; j < _len; j++) { - _hexArray[j * 2] = hexval[((d[j] >> 4) & 0xF)]; - _hexArray[j * 2 + 1] = hexval[(d[j]) & 0x0F]; - } - - _hexArray[_len * 2] = 0; - - return _hexArray; -} - - -bool hex2carray(const char *_hex, uint64_t *_bin_len, - uint8_t *_bin, uint64_t _max_length) { - - - CHECK_STATE(_hex); - CHECK_STATE(_bin); - CHECK_STATE(_bin_len) - - - uint64_t len = strnlen(_hex, 2 * _max_length + 1); - - CHECK_STATE(len != 2 * _max_length + 1); - - CHECK_STATE(len <= 2 * _max_length); - - - if (len == 0 && len % 2 == 1) - return false; - - *_bin_len = len / 2; - - for (uint64_t i = 0; i < len / 2; i++) { - int high = char2int((char) _hex[i * 2]); - int low = char2int((char) _hex[i * 2 + 1]); - - if (high < 0 || low < 0) { - return false; - } - - _bin[i] = (unsigned char) (high * 16 + low); - } - - return true; -} - bool sign_aes(const char *_encryptedKeyHex, const char *_hashHex, size_t _t, size_t _n, char *_sig) { CHECK_STATE(_encryptedKeyHex); @@ -182,13 +118,11 @@ bool sign_aes(const char *_encryptedKeyHex, const char *_hashHex, size_t _t, siz int errStatus = 0; - sgx_status_t status = SGX_SUCCESS; status = trustedBlsSignMessage(eid, &errStatus, errMsg.data(), encryptedKey, sz, xStrArg, yStrArg, signature); - HANDLE_TRUSTED_FUNCTION_ERROR(status, errStatus, errMsg.data()); string hint = BLSutils::ConvertToString(hash_with_hint.first.Y) + ":" + hash_with_hint.second; diff --git a/BLSCrypto.h b/BLSCrypto.h index 7c2d6fd9..4cfd3007 100644 --- a/BLSCrypto.h +++ b/BLSCrypto.h @@ -14,7 +14,7 @@ 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 . + along with sgxwallet. If not, see . @file BLSCrypto.h @author Stan Kladko @@ -37,13 +37,6 @@ EXTERNC bool bls_sign(const char* encryptedKeyHex, const char* hashHex, size_t t, size_t n, char* _sig); -EXTERNC int char2int(char _input); - -EXTERNC std::vector carray2Hex(const unsigned char *d, uint64_t _len); - -EXTERNC bool hex2carray(const char * _hex, uint64_t *_bin_len, - uint8_t* _bin, uint64_t _max_length ); - std::string encryptBLSKeyShare2Hex(int *errStatus, char *err_string, const char *_key); #endif //SGXWALLET_BLSCRYPTO_H diff --git a/BLSPrivateKeyShareSGX.cpp b/BLSPrivateKeyShareSGX.cpp index 5f718d3a..c22127f1 100644 --- a/BLSPrivateKeyShareSGX.cpp +++ b/BLSPrivateKeyShareSGX.cpp @@ -32,6 +32,7 @@ #include "sgxwallet.h" #include "BLSCrypto.h" +#include "CryptoTools.h" #include "ServerInit.h" #include "SEKManager.h" #include "BLSPrivateKeyShareSGX.h" @@ -122,13 +123,10 @@ string BLSPrivateKeyShareSGX::signWithHelperSGXstr( BOOST_THROW_EXCEPTION(runtime_error("Null yStr")); } - vector errMsg(BUF_LEN, 0); - SAFE_CHAR_BUF(xStrArg, BUF_LEN)SAFE_CHAR_BUF(yStrArg, BUF_LEN)SAFE_CHAR_BUF(signature, BUF_LEN); - strncpy(xStrArg, xStr->c_str(), BUF_LEN); strncpy(yStrArg, yStr->c_str(), BUF_LEN); diff --git a/BLSSignReqMessage.cpp b/BLSSignReqMessage.cpp deleted file mode 100644 index 303b8dc3..00000000 --- a/BLSSignReqMessage.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// -// Created by kladko on 15.12.20. -// - -#include "BLSSignReqMessage.h" -#include "SGXWalletServer.hpp" - - -Json::Value BLSSignReqMessage::process() { - auto keyName = getStringRapid("keyShareName"); - auto hash = getStringRapid("messageHash"); - auto t = getUint64Rapid("t"); - auto n = getUint64Rapid("n"); - auto result = SGXWalletServer::blsSignMessageHashImpl(keyName, hash, t, n); - result["type"] = ZMQMessage::BLS_SIGN_RSP; - return result; -} \ No newline at end of file diff --git a/BLSSignReqMessage.h b/BLSSignReqMessage.h deleted file mode 100644 index 3c0b6912..00000000 --- a/BLSSignReqMessage.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - 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 . - - @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& _d) : ZMQMessage(_d) {}; - - virtual Json::Value process(); - -}; - - -#endif //SGXWALLET_BLSSIGNREQMSG_H diff --git a/BLSSignRspMessage.cpp b/BLSSignRspMessage.cpp deleted file mode 100644 index 7a662e9a..00000000 --- a/BLSSignRspMessage.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - 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 . - - @file BLSRspSignMessage.h - @author Stan Kladko - @date 2020 -*/ - -#include "BLSSignRspMessage.h" -#include "SGXWalletServer.hpp" - - -Json::Value BLSSignRspMessage::process() { - assert(false); -} \ No newline at end of file diff --git a/BLSSignRspMessage.h b/BLSSignRspMessage.h deleted file mode 100644 index abf1f9d3..00000000 --- a/BLSSignRspMessage.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - 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 . - - @file BLSRspSignMessage.h - @author Stan Kladko - @date 2020 -*/ - -#ifndef SGXWALLET_BLSSIGNRSPMSG_H -#define SGXWALLET_BLSSIGNRSPMSG_H - -#include "ZMQMessage.h" - -class BLSSignRspMessage : public ZMQMessage { -public: - - BLSSignRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; - - virtual Json::Value process(); - - string getSigShare() { - return getStringRapid("signatureShare"); - } - -}; - - -#endif //SGXWALLET_BLSSIGNRSPMSG_H diff --git a/CryptoTools.cpp b/CryptoTools.cpp new file mode 100644 index 00000000..9b05ad2a --- /dev/null +++ b/CryptoTools.cpp @@ -0,0 +1,89 @@ +/* + Copyright (C) 2021-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 . + + @file CryptoTools.cpp + @author Oleh Nikolaiev + @date 2021 +*/ + +#include + +#include "common.h" +#include "CryptoTools.h" + +using std::vector; + +int char2int(char _input) { + if (_input >= '0' && _input <= '9') + return _input - '0'; + if (_input >= 'A' && _input <= 'F') + return _input - 'A' + 10; + if (_input >= 'a' && _input <= 'f') + return _input - 'a' + 10; + return -1; +} + +vector carray2Hex(const unsigned char *d, uint64_t _len) { + + CHECK_STATE(d); + + vector _hexArray( 2 * _len + 1); + + char hexval[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + for (uint64_t j = 0; j < _len; j++) { + _hexArray[j * 2] = hexval[((d[j] >> 4) & 0xF)]; + _hexArray[j * 2 + 1] = hexval[(d[j]) & 0x0F]; + } + + _hexArray[_len * 2] = 0; + + return _hexArray; +} + +bool hex2carray(const char *_hex, uint64_t *_bin_len, + uint8_t *_bin, uint64_t _max_length) { + CHECK_STATE(_hex); + CHECK_STATE(_bin); + CHECK_STATE(_bin_len) + + uint64_t len = strnlen(_hex, 2 * _max_length + 1); + + CHECK_STATE(len != 2 * _max_length + 1); + + CHECK_STATE(len <= 2 * _max_length); + + if (len % 2 == 1) + return false; + + *_bin_len = len / 2; + + for (uint64_t i = 0; i < len / 2; i++) { + int high = char2int((char) _hex[i * 2]); + int low = char2int((char) _hex[i * 2 + 1]); + + if (high < 0 || low < 0) { + return false; + } + + _bin[i] = (unsigned char) (high * 16 + low); + } + + return true; +} \ No newline at end of file diff --git a/CryptoTools.h b/CryptoTools.h new file mode 100644 index 00000000..9eed91ac --- /dev/null +++ b/CryptoTools.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2021-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 . + + @file CryptoTools.h + @author Oleh Nikolaiev + @date 2021 +*/ + +#ifndef SGXWALLET_CRYPTOTOOLS_H +#define SGXWALLET_CRYPTOTOOLS_H + +#ifdef __cplusplus +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +#include "stddef.h" +#include "stdint.h" +#include +#include + +EXTERNC int char2int(char _input); + +EXTERNC std::vector carray2Hex(const unsigned char *d, uint64_t _len); + +EXTERNC bool hex2carray(const char * _hex, uint64_t *_bin_len, + uint8_t* _bin, uint64_t _max_length ); + +#endif // SGXWALLET_CRYPTOTOOLS_H diff --git a/DKGCrypto.cpp b/DKGCrypto.cpp index 416f5cd5..72bcb1f4 100644 --- a/DKGCrypto.cpp +++ b/DKGCrypto.cpp @@ -32,7 +32,7 @@ #include "SGXException.h" #include "SGXWalletServer.hpp" -#include "BLSCrypto.h" +#include "CryptoTools.h" #include "SEKManager.h" #include "DKGCrypto.h" @@ -150,7 +150,7 @@ string gen_dkg_poly(int _t) { return result; } -vector > get_verif_vect(const string &encryptedPolyHex, int t, int n) { +vector > get_verif_vect(const string &encryptedPolyHex, int t) { auto encryptedPolyHexPtr = encryptedPolyHex.c_str(); @@ -174,7 +174,7 @@ vector > get_verif_vect(const string &encryptedPolyHex, int t, in sgx_status_t status = SGX_SUCCESS; status = trustedGetPublicShares(eid, &errStatus, errMsg.data(), encrDKGPoly.data(), encLen, - pubShares.data(), t, n); + pubShares.data(), t); HANDLE_TRUSTED_FUNCTION_ERROR(status, errStatus, errMsg.data()); @@ -189,7 +189,7 @@ vector > get_verif_vect(const string &encryptedPolyHex, int t, in } vector > getVerificationVectorMult(const std::string &encryptedPolyHex, int t, int n, size_t ind) { - auto verificationVector = get_verif_vect(encryptedPolyHex, t, n); + auto verificationVector = get_verif_vect(encryptedPolyHex, t); vector > result(t); @@ -448,7 +448,10 @@ bool createBLSShareV2(const string &blsKeyName, const char *s_shares, const char vector errMsg(BUF_LEN, 0); int errStatus = 0; - uint64_t decKeyLen;SAFE_UINT8_BUF(encr_bls_key, BUF_LEN);SAFE_UINT8_BUF(encr_key, BUF_LEN); + uint64_t decKeyLen; + SAFE_UINT8_BUF(encr_bls_key, BUF_LEN) + SAFE_UINT8_BUF(encr_key, BUF_LEN) + if (!hex2carray(encryptedKeyHex, &decKeyLen, encr_key, BUF_LEN)) { throw SGXException(CREATE_BLS_SHARE_INVALID_KEY_HEX, string(__FUNCTION__) + ":Invalid encryptedKeyHex"); } diff --git a/DKGCrypto.h b/DKGCrypto.h index f5312b07..c76b01e7 100644 --- a/DKGCrypto.h +++ b/DKGCrypto.h @@ -33,7 +33,7 @@ using namespace std; string gen_dkg_poly( int _t); -vector > get_verif_vect(const string& encryptedPolyHex, int t, int n); +vector > get_verif_vect(const string& encryptedPolyHex, int t); vector > getVerificationVectorMult(const std::string& encryptedPolyHex, int t, int n, size_t ind); diff --git a/DockerfileBase b/DockerfileBase index 942d4005..044a4d58 100644 --- a/DockerfileBase +++ b/DockerfileBase @@ -1,6 +1,6 @@ FROM ubuntu:18.04 -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install -y \ autoconf \ automake \ build-essential \ @@ -48,19 +48,19 @@ COPY . /usr/src/sdk RUN ls /usr/src/sdk/autoconf.bash WORKDIR /usr/src/sdk -RUN apt update && \ - apt install -yq apt-utils && \ - apt install -yq --no-install-recommends python-yaml vim \ - telnet git ca-certificates perl \ - reprepro libboost-all-dev alien uuid-dev libxml2-dev ccache \ - yasm flex bison libprocps-dev ccache texinfo \ - libjsonrpccpp-dev curl libjsonrpccpp-tools && \ - ln -s /usr/bin/ccache /usr/local/bin/clang && \ - ln -s /usr/bin/ccache /usr/local/bin/clang++ && \ - ln -s /usr/bin/ccache /usr/local/bin/gcc && \ - ln -s /usr/bin/ccache /usr/local/bin/g++ && \ - ln -s /usr/bin/ccache /usr/local/bin/cc && \ - ln -s /usr/bin/ccache /usr/local/bin/c++ +RUN apt update && \ + apt install -yq apt-utils && \ + apt install -yq --no-install-recommends python-yaml vim \ + telnet git ca-certificates perl \ + reprepro libboost-all-dev alien uuid-dev libxml2-dev ccache \ + yasm flex bison libprocps-dev ccache texinfo \ + libjsonrpccpp-dev curl libjsonrpccpp-tools && \ + ln -s /usr/bin/ccache /usr/local/bin/clang && \ + ln -s /usr/bin/ccache /usr/local/bin/clang++ && \ + ln -s /usr/bin/ccache /usr/local/bin/gcc && \ + ln -s /usr/bin/ccache /usr/local/bin/g++ && \ + ln -s /usr/bin/ccache /usr/local/bin/cc && \ + ln -s /usr/bin/ccache /usr/local/bin/c++ RUN cd scripts && ./build_deps.py && \ wget --progress=dot:mega -O - https://github.com/intel/dynamic-application-loader-host-interface/archive/072d233296c15d0dcd1fb4570694d0244729f87b.tar.gz | tar -xz && \ diff --git a/ECDSACrypto.cpp b/ECDSACrypto.cpp index 741f76bf..e413b83f 100644 --- a/ECDSACrypto.cpp +++ b/ECDSACrypto.cpp @@ -36,7 +36,7 @@ #include "secure_enclave/Verify.h" -#include "BLSCrypto.h" +#include "CryptoTools.h" #include "SEKManager.h" #include "ECDSACrypto.h" @@ -62,8 +62,8 @@ vector genECDSAKey() { int exportable = 0; status = trustedGenerateEcdsaKey(eid, &errStatus, errMsg.data(), - &exportable, encr_pr_key.data(), &enc_len, - pub_key_x.data(), pub_key_y.data()); + &exportable, encr_pr_key.data(), &enc_len, + pub_key_x.data(), pub_key_y.data()); HANDLE_TRUSTED_FUNCTION_ERROR(status, errStatus,errMsg.data()); diff --git a/ECDSASignReqMessage.cpp b/ECDSASignReqMessage.cpp deleted file mode 100644 index 8e99be35..00000000 --- a/ECDSASignReqMessage.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - 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 . - - @file ECDSASignReqMessage.cpp - @author Stan Kladko - @date 2020 -*/ - - -#include "SGXWalletServer.hpp" - -#include "ECDSASignReqMessage.h" - - - -Json::Value ECDSASignReqMessage::process() { - auto base = getUint64Rapid("base"); - auto keyName = getStringRapid("keyName"); - auto hash = getStringRapid("messageHash"); - auto result = SGXWalletServer::ecdsaSignMessageHashImpl(base, keyName, hash); - result["type"] = ZMQMessage::ECDSA_SIGN_RSP; - return result; -} \ No newline at end of file diff --git a/ECDSASignReqMessage.h b/ECDSASignReqMessage.h deleted file mode 100644 index 722fc75b..00000000 --- a/ECDSASignReqMessage.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright (C) 2018- 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 . - - @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 &_d) : ZMQMessage(_d) {}; - - virtual Json::Value process(); - - -}; - - -#endif //SGXWALLET_ECDSASIGNREQMESSAGE_H diff --git a/ECDSASignRspMessage.cpp b/ECDSASignRspMessage.cpp deleted file mode 100644 index c8fb7ece..00000000 --- a/ECDSASignRspMessage.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - Copyright (C) 2018- 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 . - - @file ECDSARspSignMessage.cpp - @author Stan Kladko - @date 2020 -*/ - -#include "SGXWalletServer.hpp" - -#include "ECDSASignRspMessage.h" - - - -Json::Value ECDSASignRspMessage::process() { - // never called - assert(false); -} - -string ECDSASignRspMessage::getSignature() { - - - - string r = getStringRapid( "signature_r" ); - string v = getStringRapid( "signature_v" ); - string s = getStringRapid("signature_s" ); - - auto ret = v + ":" + r.substr( 2 ) + ":" + s.substr( 2 ); - - return ret; -} diff --git a/ECDSASignRspMessage.h b/ECDSASignRspMessage.h deleted file mode 100644 index 24bcc951..00000000 --- a/ECDSASignRspMessage.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (C) 2018- 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 . - - @file ECDSARspSignMessage.h - @author Stan Kladko - @date 2020 -*/ - -#ifndef SGXWALLET_ECDSASIGNRSPMESSAGE_H -#define SGXWALLET_ECDSASIGNRSPMESSAGE_H - -#include "ZMQMessage.h" - -class ECDSASignRspMessage : public ZMQMessage { -public: - - ECDSASignRspMessage(shared_ptr &_d) : ZMQMessage(_d) {}; - - virtual Json::Value process(); - - string getSignature(); - - -}; - - -#endif //SGXWALLET_ECDSASIGNRSPMESSAGE_H diff --git a/ExitRequestedException.cpp b/ExitRequestedException.cpp new file mode 100644 index 00000000..b6db2e61 --- /dev/null +++ b/ExitRequestedException.cpp @@ -0,0 +1,29 @@ +/* + Copyright (C) 2019-Present SKALE Labs + + This file is part of sgxwallet. + + 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 . + + @file ExitRequestedException.cpp + @author Stan Kladko + @date 2018 +*/ + +#include "ExitRequestedException.h" + + +ExitRequestedException::ExitRequestedException() { + +} diff --git a/ExitRequestedException.h b/ExitRequestedException.h new file mode 100644 index 00000000..9850ee82 --- /dev/null +++ b/ExitRequestedException.h @@ -0,0 +1,31 @@ +/* + Copyright (C) 2019-Present SKALE Labs + + This file is part of sgxwallet. + + 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 . + + @file ExitRequestedException.h + @author Stan Kladko + @date 2018 +*/ + +#pragma once + +#include + +class ExitRequestedException : public std::exception { +public: + ExitRequestedException(); +}; diff --git a/LevelDB.h b/LevelDB.h index 4afd6a35..53df6b3b 100644 --- a/LevelDB.h +++ b/LevelDB.h @@ -14,7 +14,7 @@ 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 . + along with sgxwallet. If not, see . @file LevelDB.h @author Stan Kladko @@ -54,7 +54,6 @@ class LevelDB { static string sgx_data_folder; - public: static void initDataFolderAndDBs(); @@ -104,8 +103,6 @@ class LevelDB { virtual ~LevelDB(); static const string &getSgxDataFolder(); - - }; diff --git a/Makefile.am b/Makefile.am index bf00699c..ac4ac491 100644 --- a/Makefile.am +++ b/Makefile.am @@ -70,11 +70,13 @@ bin_PROGRAMS = sgxwallet testw sgx_util ## have to be explicitly listed -COMMON_SRC = SGXException.cpp ExitHandler.cpp ZMQClient.cpp BLSSignRspMessage.cpp ECDSASignRspMessage.cpp ECDSASignReqMessage.cpp BLSSignReqMessage.cpp ZMQMessage.cpp ZMQServer.cpp InvalidStateException.cpp Exception.cpp InvalidArgumentException.cpp Log.cpp \ - SGXWalletServer.cpp SGXRegistrationServer.cpp CSRManagerServer.cpp BLSCrypto.cpp \ +COMMON_SRC = SGXException.cpp ExitHandler.cpp zmq_src/ZMQClient.cpp zmq_src/RspMessage.cpp zmq_src/ReqMessage.cpp \ + zmq_src/ZMQMessage.cpp zmq_src/ZMQServer.cpp zmq_src/Agent.cpp zmq_src/WorkerThreadPool.cpp ExitRequestedException.cpp \ + InvalidStateException.cpp Exception.cpp InvalidArgumentException.cpp Log.cpp TECrypto.cpp \ + SGXWalletServer.cpp SGXRegistrationServer.cpp CSRManagerServer.cpp BLSCrypto.cpp CryptoTools.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 \ - ECDSAImpl.c TestUtils.cpp sgxwallet.c SGXInfoServer.cpp ECDSACrypto.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 ECDSAImpl.c TestUtils.cpp sgxwallet.c SGXInfoServer.cpp ECDSACrypto.cpp COMMON_ENCLAVE_SRC = secure_enclave_u.c secure_enclave_u.h sgxwallet_SOURCES = sgxwall.cpp $(COMMON_SRC) @@ -110,18 +112,20 @@ sgxwallet_LDADD=-l$(SGX_URTS_LIB) -l$(SGX_UAE_SERVICE_LIB) -LlibBLS/deps/deps_in -ljsonrpccpp-stub -ljsonrpccpp-server -ljsonrpccpp-client -ljsonrpccpp-common -ljsoncpp -lmicrohttpd \ -lboost_system -lboost_thread -lgnutls -lgcrypt -lidn2 -lcurl -lssl -lcrypto -lz -lpthread -lstdc++fs - testw_SOURCES=testw.cpp $(COMMON_SRC) nodist_testw_SOURCES=${nodist_sgxwallet_SOURCES} EXTRA_testw_DEPENDENCIES=${EXTRA_sgxwallet_DEPENDENCIES} -testw_LDADD= ${sgxwallet_LDADD} +testw_LDADD=${sgxwallet_LDADD} -sgx_util_SOURCES= SGXException.cpp ExitHandler.cpp InvalidStateException.cpp Exception.cpp InvalidArgumentException.cpp Log.cpp sgx_util.cpp stubclient.cpp LevelDB.cpp SGXRegistrationServer.cpp CSRManagerServer.cpp +sgx_util_SOURCES=SGXException.cpp ExitHandler.cpp InvalidStateException.cpp Exception.cpp \ + InvalidArgumentException.cpp Log.cpp sgx_util.cpp stubclient.cpp LevelDB.cpp \ + SGXRegistrationServer.cpp CSRManagerServer.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 + -l:libff.a -lgmp -ljsonrpccpp-stub -ljsonrpccpp-server -ljsonrpccpp-client -ljsonrpccpp-common \ + -ljsoncpp -lmicrohttpd -lgnutls -lgcrypt -lidn2 -lcurl -lssl -lcrypto -lz -lpthread -ldl diff --git a/README.md b/README.md index ae32ef4d..66739c72 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -[![Discord](https://img.shields.io/discord/534485763354787851.svg)](https://discord.gg/vvUtWJB) +[![Discord](https://img.shields.io/discord/534485763354787851.svg)](https://discord.gg/vvUtWJB) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3581/badge)](https://bestpractices.coreinfrastructure.org/projects/3581) ## Intro @@ -80,7 +80,7 @@ guide [docs/developer-guide.md](docs/developer-guide.md). ## Contributing -See [contributing](CONTRIBUTING.md) for information on how to contribute. +See [contributing](.github/CONTRIBUTING.md) for information on how to contribute. ## Libraries used by this project diff --git a/SEKManager.cpp b/SEKManager.cpp index ee7e8bff..4206aa26 100644 --- a/SEKManager.cpp +++ b/SEKManager.cpp @@ -14,7 +14,7 @@ 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 . + along with sgxwallet. If not, see . @file SEKManager.cpp @author Stan Kladko @@ -28,13 +28,12 @@ #include "third_party/spdlog/spdlog.h" - #include "sgxwallet_common.h" #include "common.h" #include "sgxwallet.h" #include "SGXException.h" -#include "BLSCrypto.h" +#include "CryptoTools.h" #include "LevelDB.h" #include "ServerDataChecker.h" @@ -76,7 +75,6 @@ void create_test_key() { LevelDB::getLevelDb()->writeDataUnique("TEST_KEY", hexEncrKey.data()); } - void validate_SEK() { shared_ptr test_key_ptr = LevelDB::getLevelDb()->readString("TEST_KEY"); @@ -112,7 +110,6 @@ void validate_SEK() { } } - shared_ptr > check_and_set_SEK(const string &SEK) { vector decr_key(BUF_LEN, 0); @@ -131,7 +128,6 @@ shared_ptr > check_and_set_SEK(const string &SEK) { SEK.c_str()); } - HANDLE_TRUSTED_FUNCTION_ERROR(status, err_status, errMsg.data()); encrypted_SEK->resize(l); @@ -151,7 +147,6 @@ void gen_SEK() { spdlog::info("Generating backup key. Will be stored in backup_key.txt ... "); - sgx_status_t status = SGX_SUCCESS; { @@ -160,7 +155,6 @@ void gen_SEK() { HANDLE_TRUSTED_FUNCTION_ERROR(status, err_status, errMsg.data()); - if (strnlen(SEK, 33) != 32) { throw SGXException(-1, "strnlen(SEK,33) != 32"); } @@ -174,24 +168,21 @@ void gen_SEK() { sek_file << SEK; - cout << "ATTENTION! YOUR BACKUP KEY HAS BEEN WRITTEN INTO sgx_data/backup_key.txt \n" << "PLEASE COPY IT TO THE SAFE PLACE AND THEN DELETE THE FILE MANUALLY BY RUNNING THE FOLLOWING COMMAND:\n" << "apt-get install secure-delete && srm -vz sgx_data/backup_key.txt" << endl; - if (!autoconfirm) { - sleep(10); string confirm_str = "I confirm"; string buffer; do { cout << " DO YOU CONFIRM THAT YOU COPIED THE KEY? (if you confirm type - I confirm)" << endl; + sleep(10); getline(cin, buffer); } while (case_insensitive_match(confirm_str, buffer)); } - LevelDB::getLevelDb()->writeDataUnique("SEK", hexEncrKey.data()); create_test_key(); @@ -242,7 +233,6 @@ void enter_SEK() { throw SGXException(CORRUPT_DATABASE, "Could not find TEST_KEY in database."); } - if (!experimental::filesystem::is_regular_file(BACKUP_PATH)) { spdlog::error("File does not exist: " BACKUP_PATH); throw SGXException(FILE_NOT_FOUND, "File does not exist: " BACKUP_PATH); diff --git a/SGXRegistrationServer.cpp b/SGXRegistrationServer.cpp index a040db89..1f8d8fe7 100644 --- a/SGXRegistrationServer.cpp +++ b/SGXRegistrationServer.cpp @@ -59,14 +59,16 @@ SGXRegistrationServer::SGXRegistrationServer(AbstractServerConnector &connector, : AbstractRegServer(connector, type), autoSign(_autoSign) {} -Json::Value signCertificateImpl(const string &_csr, bool _autoSign = false) { +Json::Value SGXRegistrationServer::SignCertificate(const string &csr) { spdlog::info(__FUNCTION__); INIT_RESULT(result) result["result"] = false; try { - string hash = cryptlite::sha256::hash_hex(_csr); + std::lock_guard lock(m); + + string hash = cryptlite::sha256::hash_hex(csr); if (system("ls " CERT_DIR "/" CERT_CREATE_COMMAND) != 0) { spdlog::error("cert/create_client_cert does not exist"); @@ -76,7 +78,7 @@ Json::Value signCertificateImpl(const string &_csr, bool _autoSign = false) { string csr_name = string(CERT_DIR) + "/" + hash + ".csr"; ofstream outfile(csr_name); outfile.exceptions(std::ifstream::failbit | std::ifstream::badbit); - outfile << _csr << endl; + outfile << csr << endl; outfile.close(); if (system(("ls " + csr_name).c_str()) != 0) { @@ -85,27 +87,27 @@ Json::Value signCertificateImpl(const string &_csr, bool _autoSign = false) { } if (system(("openssl req -in " + csr_name).c_str()) != 0) { - spdlog::error("Incorrect CSR format: {}", _csr); + spdlog::error("Incorrect CSR format: {}", csr); throw SGXException(FAIL_TO_CREATE_CERTIFICATE, "Incorrect CSR format "); } - if (_autoSign) { + if (autoSign) { string genCert = string("cd ") + CERT_DIR + "&& ./" + CERT_CREATE_COMMAND + " " + hash ; if (system(genCert.c_str()) == 0) { spdlog::info("Client cert " + hash + " generated"); - string db_key = "CSR:HASH:" + hash + "STATUS:"; - string status = "0"; - LevelDB::getCsrStatusDb()->writeDataUnique(db_key, status); } else { spdlog::error("Client cert generation failed: {} ", genCert); throw SGXException(FAIL_TO_CREATE_CERTIFICATE, "CLIENT CERTIFICATE GENERATION FAILED"); } } else { string db_key = "CSR:HASH:" + hash; - LevelDB::getCsrStatusDb()->writeDataUnique(db_key, _csr); + LevelDB::getCsrStatusDb()->writeDataUnique(db_key, csr); } + string db_key = "CSR:HASH:" + hash + "STATUS:"; + string status = "0"; + LevelDB::getCsrStatusDb()->writeDataUnique(db_key, status); result["result"] = true; result["hash"] = hash; @@ -115,7 +117,9 @@ Json::Value signCertificateImpl(const string &_csr, bool _autoSign = false) { RETURN_SUCCESS(result) } -Json::Value getCertificateImpl(const string &hash) { +Json::Value SGXRegistrationServer::GetCertificate(const string &hash) { + spdlog::info(__FUNCTION__); + Json::Value result; string cert; @@ -150,18 +154,6 @@ Json::Value getCertificateImpl(const string &hash) { RETURN_SUCCESS(result) } - -Json::Value SGXRegistrationServer::SignCertificate(const string &csr) { - spdlog::info(__FUNCTION__); - return signCertificateImpl(csr, autoSign); -} - -Json::Value SGXRegistrationServer::GetCertificate(const string &hash) { - spdlog::info(__FUNCTION__); - return getCertificateImpl(hash); -} - - void SGXRegistrationServer::initRegistrationServer(bool _autoSign) { httpServer = make_shared(BASE_PORT + 1); server = make_shared(*httpServer, @@ -190,7 +182,6 @@ int SGXRegistrationServer::exitServer() { return 0; } - shared_ptr SGXRegistrationServer::getServer() { CHECK_STATE(server); return server; diff --git a/SGXRegistrationServer.h b/SGXRegistrationServer.h index f9d9594c..e39fa9d3 100644 --- a/SGXRegistrationServer.h +++ b/SGXRegistrationServer.h @@ -39,23 +39,19 @@ using namespace jsonrpc; using namespace std; class SGXRegistrationServer : public AbstractRegServer { - recursive_mutex m; + mutex m; bool autoSign; - static shared_ptr httpServer; static shared_ptr server; - public: static shared_ptr getServer(); - SGXRegistrationServer(AbstractServerConnector &connector, serverVersion_t type, bool _autoSign = false); - virtual Json::Value SignCertificate(const string &csr); virtual Json::Value GetCertificate(const string &hash); diff --git a/SGXWalletServer.cpp b/SGXWalletServer.cpp index 820c8273..e47dfbcf 100644 --- a/SGXWalletServer.cpp +++ b/SGXWalletServer.cpp @@ -38,8 +38,9 @@ #include "SGXException.h" #include "LevelDB.h" #include "BLSCrypto.h" -#include "ECDSACrypto.h" #include "DKGCrypto.h" +#include "ECDSACrypto.h" +#include "TECrypto.h" #include "SGXWalletServer.h" #include "SGXWalletServer.hpp" @@ -49,6 +50,12 @@ #include "Log.h" +#ifdef SGX_HW_SIM +#define NUM_THREADS 16 +#else +#define NUM_THREADS 200 +#endif + using namespace std; std::shared_timed_mutex sgxInitMutex; @@ -111,20 +118,12 @@ void SGXWalletServer::printDB() { LevelDB::getLevelDb()->visitKeys(&v, 100000000); } - -#ifdef SGX_HW_SIM -#define NUM_THREADS 16 -#else -#define NUM_THREADS 200 -#endif - bool SGXWalletServer::verifyCert(string &_certFileName) { string rootCAPath = string(SGXDATA_FOLDER) + "cert_data/rootCA.pem"; string verifyCert = "cert/verify_client_cert " + rootCAPath + " " + _certFileName; return system(verifyCert.c_str()) == 0; } - void SGXWalletServer::createCertsIfNeeded() { string rootCAPath = string(SGXDATA_FOLDER) + "cert_data/rootCA.pem"; @@ -170,7 +169,6 @@ void SGXWalletServer::createCertsIfNeeded() { } } - void SGXWalletServer::initHttpsServer(bool _checkCerts) { COUNT_STATISTICS spdlog::info("Entering {}", __FUNCTION__); @@ -214,15 +212,15 @@ void SGXWalletServer::initHttpServer() { //without ssl } int SGXWalletServer::exitServer() { - spdlog::info("Stoping sgx server"); + spdlog::info("Stoping sgx server"); - if (server && !server->StopListening()) { - spdlog::error("Sgx server could not be stopped. Will forcefully terminate the app"); - } else { - spdlog::info("Sgx server stopped"); - } + if (server && !server->StopListening()) { + spdlog::error("Sgx server could not be stopped. Will forcefully terminate the app"); + } else { + spdlog::info("Sgx server stopped"); + } - return 0; + return 0; } Json::Value @@ -269,7 +267,6 @@ SGXWalletServer::importBLSKeyShareImpl(const string &_keyShare, const string &_k RETURN_SUCCESS(result); } - map SGXWalletServer::blsRequests; recursive_mutex SGXWalletServer::blsRequestsLock; @@ -288,7 +285,6 @@ void SGXWalletServer::checkForDuplicate(map &_map, recursive_mu _map[_key] = _value; } - Json::Value SGXWalletServer::blsSignMessageHashImpl(const string &_keyShareName, const string &_messageHash, int t, int n) { spdlog::trace("Entering {}", __FUNCTION__); @@ -305,10 +301,8 @@ SGXWalletServer::blsSignMessageHashImpl(const string &_keyShareName, const strin shared_ptr value = nullptr; - checkForDuplicate(blsRequests, blsRequestsLock, _keyShareName, _messageHash); - try { if (!checkName(_keyShareName, "BLS_KEY")) { throw SGXException(BLS_SIGN_INVALID_KS_NAME, string(__FUNCTION__) + ":Invalid BLSKey name"); @@ -342,9 +336,7 @@ SGXWalletServer::blsSignMessageHashImpl(const string &_keyShareName, const strin result["signatureShare"] = string(signature.data()); - RETURN_SUCCESS(result); - } Json::Value SGXWalletServer::importECDSAKeyImpl(const string &_keyShare, @@ -501,7 +493,7 @@ Json::Value SGXWalletServer::generateDKGPolyImpl(const string &_polyName, int _t RETURN_SUCCESS(result) } -Json::Value SGXWalletServer::getVerificationVectorImpl(const string &_polyName, int _t, int _n) { +Json::Value SGXWalletServer::getVerificationVectorImpl(const string &_polyName, int _t) { COUNT_STATISTICS spdlog::info("Entering {}", __FUNCTION__); INIT_RESULT(result) @@ -511,13 +503,13 @@ Json::Value SGXWalletServer::getVerificationVectorImpl(const string &_polyName, if (!checkName(_polyName, "POLY")) { throw SGXException(INVALID_DKG_GETVV_POLY_NAME, string(__FUNCTION__) + ":Invalid polynomial name"); } - if (!check_n_t(_t, _n)) { - throw SGXException(INVALID_DKG_GETVV_PARAMS, string(__FUNCTION__) + ":Invalid parameters n or t "); + if (_t <= 0) { + throw SGXException(INVALID_DKG_GETVV_PARAMS, string(__FUNCTION__) + ":Invalid t "); } shared_ptr encrPoly = readFromDb(_polyName); - verifVector = get_verif_vect(*encrPoly, _t, _n); + verifVector = get_verif_vect(*encrPoly, _t); for (int i = 0; i < _t; i++) { vector currentCoef = verifVector.at(i); @@ -648,7 +640,6 @@ SGXWalletServer::createBLSPrivateKeyImpl(const string &_blsKeyName, const string string(__FUNCTION__) + ":Error while creating BLS key share"); } - for (int i = 0; i < _n; i++) { string name = _polyName + "_" + to_string(i) + ":"; LevelDB::getLevelDb()->deleteDHDKGKey(name); @@ -978,7 +969,6 @@ SGXWalletServer::createBLSPrivateKeyV2Impl(const string &_blsKeyName, const stri string(__FUNCTION__) + ":Error while creating BLS key share"); } - for (int i = 0; i < _n; i++) { string name = _polyName + "_" + to_string(i) + ":"; LevelDB::getLevelDb()->deleteDHDKGKey(name); @@ -987,7 +977,6 @@ SGXWalletServer::createBLSPrivateKeyV2Impl(const string &_blsKeyName, const stri } LevelDB::getLevelDb()->deleteKey(_polyName); - string encryptedSecretShareName = "encryptedSecretShare:" + _polyName; LevelDB::getLevelDb()->deleteKey(encryptedSecretShareName); @@ -996,12 +985,36 @@ SGXWalletServer::createBLSPrivateKeyV2Impl(const string &_blsKeyName, const stri RETURN_SUCCESS(result); } +Json::Value SGXWalletServer::getDecryptionShareImpl(const std::string& blsKeyName, const std::string& publicDecryptionValue) { + spdlog::info("Entering {}", __FUNCTION__); + INIT_RESULT(result) + + try { + if (!checkName(blsKeyName, "BLS_KEY")) { + throw SGXException(BLS_SIGN_INVALID_KS_NAME, string(__FUNCTION__) + ":Invalid BLSKey name"); + } + + if ( publicDecryptionValue.length() < 7 || publicDecryptionValue.length() > 78 * 4 ) { + throw SGXException(INVALID_DECRYPTION_VALUE_FORMAT, string(__FUNCTION__) + ":Invalid publicDecryptionValue format"); + } + + shared_ptr encryptedKeyHex_ptr = readFromDb(blsKeyName); + + vector decryptionValueVector = calculateDecryptionShare(encryptedKeyHex_ptr->c_str(), publicDecryptionValue); + for (uint8_t i = 0; i < 4; ++i) { + result["decryptionShare"][i] = decryptionValueVector.at(i); + } + } HANDLE_SGX_EXCEPTION(result) + + RETURN_SUCCESS(result) +} + Json::Value SGXWalletServer::generateDKGPoly(const string &_polyName, int _t) { return generateDKGPolyImpl(_polyName, _t); } -Json::Value SGXWalletServer::getVerificationVector(const string &_polynomeName, int _t, int _n) { - return getVerificationVectorImpl(_polynomeName, _t, _n); +Json::Value SGXWalletServer::getVerificationVector(const string &_polynomeName, int _t) { + return getVerificationVectorImpl(_polynomeName, _t); } Json::Value SGXWalletServer::getSecretShare(const string &_polyName, const Json::Value &_publicKeys, int t, int n) { @@ -1096,11 +1109,14 @@ SGXWalletServer::createBLSPrivateKeyV2(const string &blsKeyName, const string &e return createBLSPrivateKeyV2Impl(blsKeyName, ethKeyName, polyName, SecretShare, t, n); } +Json::Value SGXWalletServer::getDecryptionShare(const std::string& blsKeyName, const std::string& publicDecryptionValue) { + return getDecryptionShareImpl(blsKeyName, publicDecryptionValue); +} + shared_ptr SGXWalletServer::readFromDb(const string &name, const string &prefix) { auto dataStr = checkDataFromDb(prefix + name); if (dataStr == nullptr) { - throw SGXException(KEY_SHARE_DOES_NOT_EXIST, string(__FUNCTION__) + ":Data with this name does not exist: " + prefix + name); } @@ -1124,9 +1140,9 @@ void SGXWalletServer::writeKeyShare(const string &_keyShareName, const string &_ } void SGXWalletServer::writeDataToDB(const string &name, const string &value) { - if (LevelDB::getLevelDb()->readString(name) != nullptr) { throw SGXException(KEY_NAME_ALREADY_EXISTS, string(__FUNCTION__) + ":Name already exists" + name); } + LevelDB::getLevelDb()->writeString(name, value); } diff --git a/SGXWalletServer.h b/SGXWalletServer.h index 29a6970a..ac1fe47b 100644 --- a/SGXWalletServer.h +++ b/SGXWalletServer.h @@ -14,7 +14,7 @@ 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 . + along with sgxwallet. If not, see . @file SGXWalletServer.h @author Stan Kladko diff --git a/SGXWalletServer.hpp b/SGXWalletServer.hpp index 69073a5a..bcec3f59 100644 --- a/SGXWalletServer.hpp +++ b/SGXWalletServer.hpp @@ -14,7 +14,7 @@ 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 . + along with sgxwallet. If not, see . @file SGXWalletServer.hpp @author Stan Kladko @@ -48,12 +48,9 @@ class SGXWalletServer : public AbstractStubServer { static map ecdsaRequests; static recursive_mutex ecdsaRequestsLock; - - static void checkForDuplicate(map &_map, recursive_mutex &_m, const string &_key, const string &_value); - public: static bool verifyCert(string& _certFileName); @@ -82,7 +79,7 @@ class SGXWalletServer : public AbstractStubServer { virtual Json::Value generateDKGPoly(const string &_polyName, int _t); - virtual Json::Value getVerificationVector(const string &_polynomeName, int _t, int _n); + virtual Json::Value getVerificationVector(const string &_polynomeName, int _t); virtual Json::Value getSecretShare(const string &_polyName, const Json::Value &_publicKeys, int t, int n); @@ -116,6 +113,8 @@ class SGXWalletServer : public AbstractStubServer { virtual Json::Value createBLSPrivateKeyV2(const std::string& blsKeyName, const std::string& ethKeyName, const std::string& polyName, const std::string & SecretShare, int t, int n); + virtual Json::Value getDecryptionShare(const std::string& blsKeyName, const std::string& publicDecryptionValue); + static shared_ptr readFromDb(const string &name, const string &prefix = ""); static shared_ptr checkDataFromDb(const string &name, const string &prefix = ""); @@ -140,7 +139,7 @@ class SGXWalletServer : public AbstractStubServer { static Json::Value generateDKGPolyImpl(const string &_polyName, int _t); - static Json::Value getVerificationVectorImpl(const string &_polyName, int _t, int _n); + static Json::Value getVerificationVectorImpl(const string &_polyName, int _t); static Json::Value getSecretShareImpl(const string &_polyName, const Json::Value &_pubKeys, int _t, int _n); @@ -172,7 +171,9 @@ class SGXWalletServer : public AbstractStubServer { static Json::Value dkgVerificationV2Impl(const string &_publicShares, const string &_ethKeyName, const string &_secretShare, int _t, int _n, int _index); - virtual Json::Value createBLSPrivateKeyV2Impl(const std::string& blsKeyName, const std::string& ethKeyName, const std::string& polyName, const std::string & SecretShare, int t, int n); + static Json::Value createBLSPrivateKeyV2Impl(const std::string& blsKeyName, const std::string& ethKeyName, const std::string& polyName, const std::string & SecretShare, int t, int n); + + static Json::Value getDecryptionShareImpl(const std::string& KeyName, const std::string& publicDecryptionValue); static void printDB(); diff --git a/ServerInit.cpp b/ServerInit.cpp index 99e1211e..1167a6e1 100644 --- a/ServerInit.cpp +++ b/ServerInit.cpp @@ -14,7 +14,7 @@ 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 . + along with sgxwallet. If not, see . @file ServerInit.cpp @author Stan Kladko @@ -58,7 +58,7 @@ #include "BLSCrypto.h" #include "ServerInit.h" #include "SGXException.h" -#include "ZMQServer.h" +#include "zmq_src/ZMQServer.h" #include "SGXWalletServer.hpp" uint32_t enclaveLogLevel = 0; @@ -103,10 +103,8 @@ void initUserSpace() { } - uint64_t initEnclave() { - #ifndef SGX_HW_SIM unsigned long support; support = get_sgx_support(); @@ -161,10 +159,8 @@ uint64_t initEnclave() { return SGX_SUCCESS; } - void initAll(uint32_t _logLevel, bool _checkCert, - bool _checkZMQSig, bool _autoSign, bool _generateTestKeys) { - + bool _checkZMQSig, bool _autoSign, bool _generateTestKeys, bool _checkKeyOwnership) { static atomic sgxServerInited(false); static mutex initMutex; @@ -213,7 +209,7 @@ void initAll(uint32_t _logLevel, bool _checkCert, SGXRegistrationServer::initRegistrationServer(_autoSign); CSRManagerServer::initCSRManagerServer(); SGXInfoServer::initInfoServer(_logLevel, _checkCert, _autoSign, _generateTestKeys); - ZMQServer::initZMQServer(_checkZMQSig); + ZMQServer::initZMQServer(_checkZMQSig, _checkKeyOwnership); sgxServerInited = true; } catch (SGXException &_e) { @@ -237,5 +233,4 @@ void exitAll() { CSRManagerServer::exitServer(); SGXInfoServer::exitServer(); ZMQServer::exitZMQServer(); - } diff --git a/ServerInit.h b/ServerInit.h index 83241483..16b94eb5 100644 --- a/ServerInit.h +++ b/ServerInit.h @@ -32,7 +32,7 @@ #define EXTERNC #endif -EXTERNC void initAll(uint32_t _logLevel, bool _checkCert, bool _checkZMQSig, bool _autoSign, bool _generateTestKeys); +EXTERNC void initAll(uint32_t _logLevel, bool _checkCert, bool _checkZMQSig, bool _autoSign, bool _generateTestKeys, bool _checkKeyOwnership); void exitAll(); diff --git a/TECrypto.cpp b/TECrypto.cpp new file mode 100644 index 00000000..2dc0bb34 --- /dev/null +++ b/TECrypto.cpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2021-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 . + + @file TECrypto.cpp + @author Oleh Nikolaiev + @date 2021 +*/ + +#include +#include "leveldb/db.h" +#include + +#include "threshold_encryption/threshold_encryption.h" + +#include "sgxwallet_common.h" +#include "sgxwallet.h" +#include "SGXException.h" +#include "third_party/spdlog/spdlog.h" +#include "common.h" +#include "SGXWalletServer.h" + +#include "TECrypto.h" +#include "CryptoTools.h" + +#include + +vector calculateDecryptionShare(const string& encryptedKeyShare, + const string& publicDecryptionValue) { + size_t sz = 0; + + SAFE_UINT8_BUF(encryptedKey, BUF_LEN); + + bool result = hex2carray(encryptedKeyShare.data(), &sz, encryptedKey, BUF_LEN); + + if (!result) { + BOOST_THROW_EXCEPTION(invalid_argument("Invalid hex encrypted key")); + } + + SAFE_CHAR_BUF(decryptionShare, BUF_LEN) + + vector errMsg(BUF_LEN, 0); + + int errStatus = 0; + + sgx_status_t status = SGX_SUCCESS; + + status = trustedGetDecryptionShare(eid, &errStatus, errMsg.data(), encryptedKey, + publicDecryptionValue.data(), sz, decryptionShare); + + HANDLE_TRUSTED_FUNCTION_ERROR(status, errStatus, errMsg.data()); + + auto splitted_share = BLSutils::SplitString(std::make_shared(decryptionShare), ":"); + + return *splitted_share; +} \ No newline at end of file diff --git a/TECrypto.h b/TECrypto.h new file mode 100644 index 00000000..4c03dd57 --- /dev/null +++ b/TECrypto.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2021-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 . + + @file TECrypto.h + @author Oleh Nikolaiev + @date 2021 +*/ + +#ifndef SGXWALLET_TECRYPTO_H +#define SGXWALLET_TECRYPTO_H + +#ifdef __cplusplus +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +#include "stddef.h" +#include "stdint.h" +#include +#include + +std::vector calculateDecryptionShare(const std::string& encryptedKeyShare, + const std::string& publicDecryptionValue); + +#endif // SGXWALLET_TECRYPTO_H diff --git a/TestUtils.cpp b/TestUtils.cpp index 50814563..86b9ccb3 100644 --- a/TestUtils.cpp +++ b/TestUtils.cpp @@ -14,7 +14,7 @@ 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 . + along with sgxwallet. If not, see . @file TestUtils.cpp @author Stan Kladko @@ -37,12 +37,12 @@ #include "BLSCrypto.h" #include "ServerInit.h" #include "DKGCrypto.h" +#include "CryptoTools.h" #include "SGXException.h" #include "LevelDB.h" #include "SGXWalletServer.hpp" #include "catch.hpp" -#include "ZMQClient.h" #include "BLSSigShare.h" #include "BLSSigShareSet.h" #include "BLSPublicKeyShare.h" @@ -73,7 +73,6 @@ string TestUtils::stringFromFr(libff::alt_bn128_Fr &el) { return string(arr); } - string TestUtils::convertDecToHex(string dec, int numBytes) { mpz_t num; mpz_init(num); @@ -190,7 +189,6 @@ void TestUtils::sendRPCRequest() { CHECK_STATE(sig["status"].asInt() == 0); } - CHECK_STATE(ethKeys[i]["status"] == 0); string polyName = "POLY:SCHAIN_ID:" + to_string(schainID) + ":NODE_ID:" + to_string(i) + ":DKG_ID:" + to_string(dkgID); @@ -199,7 +197,7 @@ void TestUtils::sendRPCRequest() { polyNames[i] = polyName; for (int i3 = 0; i3 <= testCount; i3++) { - verifVects[i] = c.getVerificationVector(polyName, t, n); + verifVects[i] = c.getVerificationVector(polyName, t); CHECK_STATE(verifVects[i]["status"] == 0); } @@ -250,7 +248,6 @@ void TestUtils::sendRPCRequest() { publicShares["publicShares"][i] = pubShares[i]; } - Json::Value blsPublicKeys; for (int i6 = 0; i6 <= testCount; i6++) { @@ -263,7 +260,6 @@ void TestUtils::sendRPCRequest() { string blsName = "BLS_KEY" + polyNames[i].substr(4); string secretShare = secretShares[i]["secretShare"].asString(); - auto response = c.createBLSPrivateKey(blsName, ethKeys[i]["keyName"].asString(), polyNames[i], secShares[i], t, n); CHECK_STATE(response["status"] == 0); @@ -321,7 +317,7 @@ void TestUtils::sendRPCRequestV2() { auto response = c.generateDKGPoly(polyName, t); CHECK_STATE(response["status"] == 0); polyNames[i] = polyName; - verifVects[i] = c.getVerificationVector(polyName, t, n); + verifVects[i] = c.getVerificationVector(polyName, t); CHECK_STATE(verifVects[i]["status"] == 0); pubEthKeys.append(ethKeys[i]["publicKey"]); @@ -399,47 +395,41 @@ void TestUtils::sendRPCRequestV2() { sigShareSet.merge(); } - void TestUtils::sendRPCRequestZMQ() { - HttpClient client(RPC_ENDPOINT); - StubClient c(client, JSONRPC_CLIENT_V2); - - - + auto client = make_shared(ZMQ_IP, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", + "./sgx_data/cert_data/rootCA.key"); int n = 16, t = 16; - Json::Value ethKeys[n]; + vector ethKeys(n); Json::Value verifVects[n]; Json::Value pubEthKeys; - Json::Value secretShares[n]; + vector secretShares(n); Json::Value pubBLSKeys[n]; - Json::Value blsSigShares[n]; - vector pubShares(n); - vector polyNames(n); + vector blsSigShares(n); + vector pubShares(n); + vector polyNames(n); static atomic counter(1); int schainID = counter.fetch_add(1); int dkgID = counter.fetch_add(1); for (uint8_t i = 0; i < n; i++) { - ethKeys[i] = c.generateECDSAKey(); - CHECK_STATE(ethKeys[i]["status"] == 0); + auto generatedKey = client->generateECDSAKey(); + ethKeys[i] = generatedKey.second; string polyName = "POLY:SCHAIN_ID:" + to_string(schainID) + ":NODE_ID:" + to_string(i) + ":DKG_ID:" + to_string(dkgID); - auto response = c.generateDKGPoly(polyName, t); - CHECK_STATE(response["status"] == 0); + CHECK_STATE(client->generateDKGPoly(polyName, t)); polyNames[i] = polyName; - verifVects[i] = c.getVerificationVector(polyName, t, n); - CHECK_STATE(verifVects[i]["status"] == 0); + verifVects[i] = client->getVerificationVector(polyName, t); - pubEthKeys.append(ethKeys[i]["publicKey"]); + pubEthKeys.append(generatedKey.first); } for (uint8_t i = 0; i < n; i++) { - secretShares[i] = c.getSecretShareV2(polyNames[i], pubEthKeys, t, n); + secretShares[i] = client->getSecretShare(polyNames[i], pubEthKeys, t, n); for (uint8_t k = 0; k < t; k++) { for (uint8_t j = 0; j < 4; j++) { - string pubShare = verifVects[i]["verificationVector"][k][j].asString(); + string pubShare = verifVects[i][k][j].asString(); pubShares[i] += convertDecToHex(pubShare); } } @@ -449,10 +439,10 @@ void TestUtils::sendRPCRequestZMQ() { for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { - string secretShare = secretShares[i]["secretShare"].asString().substr(192 * j, 192); - secShares[i] += secretShares[j]["secretShare"].asString().substr(192 * i, 192); - Json::Value verif = c.dkgVerificationV2(pubShares[i], ethKeys[j]["keyName"].asString(), secretShare, t, n, j); - CHECK_STATE(verif["status"] == 0); + string secretShare = secretShares[i].substr(192 * j, 192); + secShares[i] += secretShares[j].substr(192 * i, 192); + bool verif = client->dkgVerification(pubShares[i], ethKeys[j], secretShare, t, n, j); + CHECK_STATE(verif); } BLSSigShareSet sigShareSet(t, n); @@ -471,17 +461,31 @@ void TestUtils::sendRPCRequestZMQ() { for (int i = 0; i < n; ++i) { publicShares["publicShares"][i] = pubShares[i]; } - - Json::Value blsPublicKeys = c.calculateAllBLSPublicKeys(publicShares, t, n); - CHECK_STATE(blsPublicKeys["status"] == 0); + + Json::Value blsPublicKeys = client->getAllBlsPublicKeys(publicShares, t, n); for (int i = 0; i < t; i++) { string blsName = "BLS_KEY" + polyNames[i].substr(4); + string secretShare = secretShares[i]; + + CHECK_STATE(client->createBLSPrivateKey(blsName, ethKeys[i], polyNames[i], secShares[i], t, n)); + pubBLSKeys[i] = client->getBLSPublicKey(blsName); + + libff::alt_bn128_G2 publicKey(libff::alt_bn128_Fq2(libff::alt_bn128_Fq(pubBLSKeys[i][0].asCString()), + libff::alt_bn128_Fq(pubBLSKeys[i][1].asCString())), + libff::alt_bn128_Fq2(libff::alt_bn128_Fq(pubBLSKeys[i][2].asCString()), + libff::alt_bn128_Fq(pubBLSKeys[i][3].asCString())), + libff::alt_bn128_Fq2::one()); + + string public_key_str = convertG2ToString(publicKey); + + CHECK_STATE(public_key_str == blsPublicKeys[i].asString()); + string hash = SAMPLE_HASH; - blsSigShares[i] = c.blsSignMessageHash(blsName, hash, t, n); - CHECK_STATE(blsSigShares[i]["status"] == 0); + blsSigShares[i] = client->blsSignMessageHash(blsName, hash, t, n); + CHECK_STATE(blsSigShares[i].length() > 0); - shared_ptr sig_share_ptr = make_shared(blsSigShares[i]["signatureShare"].asString()); + shared_ptr sig_share_ptr = make_shared(blsSigShares[i]); BLSSigShare sig(sig_share_ptr, i + 1, t, n); sigShareSet.addSigShare(make_shared(sig)); } @@ -527,7 +531,7 @@ void TestUtils::doDKG(StubClient &c, int n, int t, Json::Value response = c.generateDKGPoly(polyName, t); CHECK_STATE(response["status"] == 0); polyNames[i] = polyName; - verifVects[i] = c.getVerificationVector(polyName, t, n); + verifVects[i] = c.getVerificationVector(polyName, t); CHECK_STATE(verifVects[i]["status"] == 0); pubEthKeys.append(ethKeys[i]["publicKey"]); } @@ -668,7 +672,7 @@ void TestUtils::doDKGV2(StubClient &c, int n, int t, Json::Value response = c.generateDKGPoly(polyName, t); CHECK_STATE(response["status"] == 0); polyNames[i] = polyName; - verifVects[i] = c.getVerificationVector(polyName, t, n); + verifVects[i] = c.getVerificationVector(polyName, t); CHECK_STATE(verifVects[i]["status"] == 0); pubEthKeys.append(ethKeys[i]["publicKey"]); } @@ -810,7 +814,7 @@ void TestUtils::doZMQBLS(shared_ptr _zmqClient, StubClient &c, int n, Json::Value response = c.generateDKGPoly(polyName, t); CHECK_STATE(response["status"] == 0); polyNames[i] = polyName; - verifVects[i] = c.getVerificationVector(polyName, t, n); + verifVects[i] = c.getVerificationVector(polyName, t); CHECK_STATE(verifVects[i]["status"] == 0); pubEthKeys.append(ethKeys[i]["publicKey"]); } diff --git a/TestUtils.h b/TestUtils.h index 29830434..9c004d63 100644 --- a/TestUtils.h +++ b/TestUtils.h @@ -25,10 +25,7 @@ #define SGXWALLET_TESTUTILS_H #include -#include #include -// #include -// #include #include #include "sgxwallet_common.h" #include "third_party/intel/create_enclave.h" @@ -41,7 +38,7 @@ #include #include "stubclient.h" #include -#include "ZMQClient.h" +#include "zmq_src/ZMQClient.h" #include "abstractstubserver.h" using namespace std; diff --git a/VERSION b/VERSION index 9c47a6d3..6b4de0a4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.77.1 \ No newline at end of file +1.83.0 diff --git a/ZMQMessage.cpp b/ZMQMessage.cpp deleted file mode 100644 index b7605f7c..00000000 --- a/ZMQMessage.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - 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 . - - @file ZMQMessage.cpp - @author Stan Kladko - @date 2020 -*/ - -#include "common.h" -#include "sgxwallet_common.h" -#include -#include -#include - -#include "ZMQClient.h" -#include "SGXWalletServer.hpp" -#include "BLSSignReqMessage.h" -#include "BLSSignRspMessage.h" -#include "ECDSASignReqMessage.h" -#include "ECDSASignRspMessage.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::parse(const char *_msg, - size_t _size, bool _isRequest, - bool _verifySig) { - - CHECK_STATE(_msg); - CHECK_STATE2(_size > 5, ZMQ_INVALID_MESSAGE_SIZE); - // CHECK NULL TERMINATED - CHECK_STATE(_msg[_size] == 0); - CHECK_STATE2(_msg[_size - 1] == '}', ZMQ_INVALID_MESSAGE); - CHECK_STATE2(_msg[0] == '{', ZMQ_INVALID_MESSAGE); - - auto d = make_shared(); - - d->Parse(_msg); - - CHECK_STATE2(!d->HasParseError(), ZMQ_COULD_NOT_PARSE); - CHECK_STATE2(d->IsObject(), ZMQ_COULD_NOT_PARSE); - - CHECK_STATE2(d->HasMember("type"), ZMQ_NO_TYPE_IN_MESSAGE); - CHECK_STATE2((*d)["type"].IsString(), ZMQ_NO_TYPE_IN_MESSAGE); - string type = (*d)["type"].GetString(); - - if (_verifySig) { - CHECK_STATE2(d->HasMember("cert"),ZMQ_NO_CERT_IN_MESSAGE); - CHECK_STATE2(d->HasMember("msgSig"), ZMQ_NO_SIG_IN_MESSAGE); - CHECK_STATE2((*d)["cert"].IsString(), ZMQ_NO_CERT_IN_MESSAGE); - CHECK_STATE2((*d)["msgSig"].IsString(), ZMQ_NO_SIG_IN_MESSAGE); - - auto cert = make_shared((*d)["cert"].GetString()); - string hash = cryptlite::sha256::hash_hex(*cert); - - auto filepath = "/tmp/sgx_wallet_cert_hash_" + hash; - - std::ofstream outFile(filepath); - - outFile << *cert; - - outFile.close(); - - static recursive_mutex m; - - EVP_PKEY *publicKey = nullptr; - - { - lock_guard lock(m); - - if (!verifiedCerts.exists(*cert)) { - CHECK_STATE(SGXWalletServer::verifyCert(filepath)); - auto handles = ZMQClient::readPublicKeyFromCertStr(*cert); - CHECK_STATE(handles.first); - CHECK_STATE(handles.second); - verifiedCerts.put(*cert, handles); - remove(cert->c_str()); - } - - publicKey = verifiedCerts.get(*cert).first; - - CHECK_STATE(publicKey); - - auto msgSig = make_shared((*d)["msgSig"].GetString()); - - d->RemoveMember("msgSig"); - - rapidjson::StringBuffer buffer; - - rapidjson::Writer w(buffer); - - d->Accept(w); - - auto msgToVerify = buffer.GetString(); - - ZMQClient::verifySig(publicKey,msgToVerify, *msgSig ); - - } - } - - - shared_ptr result; - - if (_isRequest) { - return buildRequest(type, d); - } else { - return buildResponse(type, d); - } -} - -shared_ptr ZMQMessage::buildRequest(string &_type, shared_ptr _d) { - if (_type == ZMQMessage::BLS_SIGN_REQ) { - return make_shared(_d); - } else if (_type == ZMQMessage::ECDSA_SIGN_REQ) { - return - make_shared(_d); - } else { - BOOST_THROW_EXCEPTION(SGXException(-301, "Incorrect zmq message type: " + - string(_type))); - } -} - -shared_ptr ZMQMessage::buildResponse(string &_type, shared_ptr _d) { - if (_type == ZMQMessage::BLS_SIGN_RSP) { - return - make_shared(_d); - } else if (_type == ZMQMessage::ECDSA_SIGN_RSP) { - return - make_shared(_d); - } else { - BOOST_THROW_EXCEPTION(InvalidStateException("Incorrect zmq message request type: " + string(_type), - __CLASS_NAME__) - ); - } -} - -cache::lru_cache> -ZMQMessage::verifiedCerts(256); \ No newline at end of file diff --git a/ZMQMessage.h b/ZMQMessage.h deleted file mode 100644 index e2ce6a87..00000000 --- a/ZMQMessage.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - 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 . - - @file ZMQMessage.h - @author Stan Kladko - @date 2018 -*/ - -#pragma once - - -#include -#include - -#include -#include -#include -#include -#include - -#include "third_party/lrucache.hpp" - -#include "abstractstubserver.h" - -#include "document.h" -#include "stringbuffer.h" -#include "writer.h" - -#include "SGXException.h" - -using namespace std; - -class ZMQMessage { - - shared_ptr d; - - - static cache::lru_cache> verifiedCerts; - -protected: - - -public: - - - 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"; - - explicit ZMQMessage(shared_ptr &_d) : d(_d) { - }; - - string getStringRapid(const char *_name); - - uint64_t getUint64Rapid(const char *_name); - - uint64_t getStatus() { - return getUint64Rapid("status"); - } - - static shared_ptr parse(const char* _msg, size_t _size, bool _isRequest, - bool _verifySig); - - static shared_ptr buildRequest(string& type, shared_ptr _d); - static shared_ptr buildResponse(string& type, shared_ptr _d); - - virtual Json::Value process() = 0; - -}; \ No newline at end of file diff --git a/ZMQServer.cpp b/ZMQServer.cpp deleted file mode 100644 index 3ffe0402..00000000 --- a/ZMQServer.cpp +++ /dev/null @@ -1,263 +0,0 @@ -/* - 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 . - - @file ZMQServer.cpp - @author Stan Kladko - @date 2019 -*/ - -#include -#include - - -#include "third_party/spdlog/spdlog.h" - -#include "common.h" - -#include "SGXException.h" -#include "ZMQMessage.h" -#include "ZMQServer.h" -#include "sgxwallet_common.h" - -using namespace std; - -shared_ptr ZMQServer::zmqServer = nullptr; - -ZMQServer::ZMQServer(bool _checkSignature, const string &_caCertFile) - : checkSignature(_checkSignature), - caCertFile(_caCertFile), ctx(make_shared(1)) { - - - socket = make_shared(*ctx, ZMQ_ROUTER); - - if (_checkSignature) { - CHECK_STATE(!_caCertFile.empty()); - ifstream t(_caCertFile); - string str((istreambuf_iterator(t)), istreambuf_iterator()); - caCert = str; - CHECK_STATE(!caCert.empty()) - } - - int linger = 0; - - zmq_setsockopt(*socket, ZMQ_LINGER, &linger, sizeof(linger)); - -} - - -void ZMQServer::run() { - - auto port = BASE_PORT + 5; - - spdlog::info("Starting zmq server on port {} ...", port); - - try { - CHECK_STATE(socket); - socket->bind("tcp://*:" + to_string(port)); - } catch (...) { - spdlog::error("Server task could not bind to port:{}", port); - throw SGXException(ZMQ_COULD_NOT_BIND_FRONT_END, "Server task could not bind."); - } - - spdlog::info("Bound port ..."); - - while (!isExitRequested) { - try { - zmqServer->doOneServerLoop(); - } catch (...) { - spdlog::error("doOneServerLoop threw exception. This should never happen!"); - } - } - - spdlog::info("Exited zmq server loop"); - -} - - -std::atomic ZMQServer::isExitRequested(false); - -void ZMQServer::exitZMQServer() { - isExitRequested.exchange(true); - zmqServer->ctx->shutdown(); - zmqServer->socket->close(); - zmqServer->ctx->close(); - spdlog::info("Exited zmq server."); -} - - -void ZMQServer::initZMQServer(bool _checkSignature) { - static bool initedServer = false; - CHECK_STATE(!initedServer) - initedServer = true; - - spdlog::info("Initing zmq server. checkSignature is set to {}", _checkSignature); - - string rootCAPath = ""; - - if (_checkSignature) { - rootCAPath = string(SGXDATA_FOLDER) + "cert_data/rootCA.pem"; - spdlog::info("Reading root CA from {}", rootCAPath); - CHECK_STATE(access(rootCAPath.c_str(), F_OK) == 0); - }; - - zmqServer = make_shared(_checkSignature, rootCAPath); - - CHECK_STATE(zmqServer) - - serverThread = make_shared(std::bind(&ZMQServer::run, ZMQServer::zmqServer)); - serverThread->detach(); - - spdlog::info("Inited zmq server ..."); - - -} - -shared_ptr ZMQServer::serverThread = nullptr; - -ZMQServer::~ZMQServer() { -} - - -void ZMQServer::doOneServerLoop() { - - string replyStr; - - Json::Value result; - result["status"] = ZMQ_SERVER_ERROR; - result["errorMessage"] = ""; - - zmq::message_t identity; - zmq::message_t identit2; - zmq::message_t copied_id; - - - string stringToParse = ""; - - try { - - zmq_pollitem_t items[1]; - items[0].socket = *socket; - items[0].events = ZMQ_POLLIN; - - int pollResult = 0; - - - do { - pollResult = zmq_poll(items, 1, 1000); - if (isExitRequested) { - return; - } - } while (pollResult == 0); - - - if (!socket->recv(&identity)) { - // something terrible happened - spdlog::error("Fatal error: socket->recv(&identity) returned false"); - exit(-11); - } - - - if (!identity.more()) { - // something terrible happened - spdlog::error("Fatal error: zmq_msg_more(identity) returned false"); - exit(-12); - } - - - copied_id.copy(&identity); - - zmq::message_t reqMsg; - - if (!socket->recv(&reqMsg, 0)) { - // something terrible happened - spdlog::error("Fatal error: socket.recv(&reqMsg, 0) returned false"); - exit(-13); - } - - - stringToParse = string((char *) reqMsg.data(), reqMsg.size()); - - CHECK_STATE(stringToParse.front() == '{') - CHECK_STATE(stringToParse.back() == '}') - - auto parsedMsg = ZMQMessage::parse( - stringToParse.c_str(), stringToParse.size(), true, checkSignature); - - CHECK_STATE2(parsedMsg, ZMQ_COULD_NOT_PARSE); - - result = parsedMsg->process(); - } - catch (std::exception &e) { - if (isExitRequested) { - return; - } - result["errorMessage"] = string(e.what()); - spdlog::error("Exception in zmq server :{}", e.what()); - spdlog::error("ID:" + string((char*) identity.data(), identity.size())); - spdlog::error("Client request :" + stringToParse); - - } catch (...) { - if (isExitRequested) { - return; - } - spdlog::error("Error in zmq server "); - result["errorMessage"] = "Error in zmq server "; - spdlog::error("ID:" + string((char*) identity.data(), identity.size())); - spdlog::error("Client request :" + stringToParse); - } - - try { - - Json::FastWriter fastWriter; - fastWriter.omitEndingLineFeed(); - - replyStr = fastWriter.write(result); - - CHECK_STATE(replyStr.size() > 2); - CHECK_STATE(replyStr.front() == '{'); - CHECK_STATE(replyStr.back() == '}'); - - if (!socket->send(copied_id, ZMQ_SNDMORE)) { - if (isExitRequested) { - return; - } - exit(-15); - } - if (!s_send(*socket, replyStr)) { - if (isExitRequested) { - return; - } - exit(-16); - } - - } catch ( - std::exception &e - ) { - if (isExitRequested) { - return; - } - spdlog::error("Exception in zmq server worker send :{}", e.what()); - exit(-17); - } catch (...) { - if (isExitRequested) { - return; - } - spdlog::error("Unklnown exception in zmq server worker send"); - exit(-18); - } -} diff --git a/ZMQServer.h b/ZMQServer.h deleted file mode 100644 index c3d22bbe..00000000 --- a/ZMQServer.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - 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 . - - @file ZMQServer.h - @author Stan Kladko - @date 2020 -*/ - - -#ifndef SGXWALLET_ZMQServer_H -#define SGXWALLET_ZMQServer_H - - -#include -#include -#include -#include -#include - -#include -#include "zhelpers.hpp" - -using namespace std; - - -class ZMQServer { - - uint64_t workerThreads; - -public: - - bool checkSignature = false; - string caCertFile = ""; - string caCert = ""; - - static shared_ptr zmqServer; - - static shared_ptr serverThread; - - ZMQServer(bool _checkSignature, const string& _caCertFile); - - ~ZMQServer(); - - void run(); - - static void initZMQServer(bool _checkSignature); - static void exitZMQServer(); - - - -private: - shared_ptr ctx; - shared_ptr socket; - - static std::atomic isExitRequested; - - void doOneServerLoop(); - -}; - - - -#endif //SGXWALLET_ZMQServer_H diff --git a/abstractstubserver.h b/abstractstubserver.h index a99fb0f0..e6d3d0cf 100644 --- a/abstractstubserver.h +++ b/abstractstubserver.h @@ -14,7 +14,7 @@ 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 . + along with sgxwallet. If not, see . @file BLSEnclave.cpp @author Stan Kladko @@ -45,7 +45,7 @@ class AbstractStubServer : public jsonrpc::AbstractServer this->bindAndAddMethod(jsonrpc::Procedure("ecdsaSignMessageHash", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "base",jsonrpc::JSON_INTEGER,"keyName",jsonrpc::JSON_STRING,"messageHash",jsonrpc::JSON_STRING, NULL), &AbstractStubServer::ecdsaSignMessageHashI); this->bindAndAddMethod(jsonrpc::Procedure("generateDKGPoly", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "polyName",jsonrpc::JSON_STRING,"t",jsonrpc::JSON_INTEGER, NULL), &AbstractStubServer::generateDKGPolyI); - this->bindAndAddMethod(jsonrpc::Procedure("getVerificationVector", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT,"polyName",jsonrpc::JSON_STRING, "t",jsonrpc::JSON_INTEGER, NULL), &AbstractStubServer::getVerificationVectorI); + this->bindAndAddMethod(jsonrpc::Procedure("getVerificationVector", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "polyName", jsonrpc::JSON_STRING, "t", jsonrpc::JSON_INTEGER, NULL), &AbstractStubServer::getVerificationVectorI); this->bindAndAddMethod(jsonrpc::Procedure("getSecretShare", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "polyName",jsonrpc::JSON_STRING,"publicKeys",jsonrpc::JSON_ARRAY, "n",jsonrpc::JSON_INTEGER,"t",jsonrpc::JSON_INTEGER, NULL), &AbstractStubServer::getSecretShareI); this->bindAndAddMethod(jsonrpc::Procedure("dkgVerification", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "publicShares",jsonrpc::JSON_STRING, "ethKeyName",jsonrpc::JSON_STRING, "secretShare",jsonrpc::JSON_STRING,"t",jsonrpc::JSON_INTEGER, "n",jsonrpc::JSON_INTEGER, "index",jsonrpc::JSON_INTEGER, NULL), &AbstractStubServer::dkgVerificationI); this->bindAndAddMethod(jsonrpc::Procedure("createBLSPrivateKey", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "blsKeyName",jsonrpc::JSON_STRING, "ethKeyName",jsonrpc::JSON_STRING, "polyName", jsonrpc::JSON_STRING, "secretShare",jsonrpc::JSON_STRING,"t", jsonrpc::JSON_INTEGER,"n",jsonrpc::JSON_INTEGER, NULL), &AbstractStubServer::createBLSPrivateKeyI); @@ -62,6 +62,8 @@ class AbstractStubServer : public jsonrpc::AbstractServer this->bindAndAddMethod(jsonrpc::Procedure("getSecretShareV2", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "polyName",jsonrpc::JSON_STRING,"publicKeys",jsonrpc::JSON_ARRAY, "n",jsonrpc::JSON_INTEGER,"t",jsonrpc::JSON_INTEGER, NULL), &AbstractStubServer::getSecretShareV2I); this->bindAndAddMethod(jsonrpc::Procedure("dkgVerificationV2", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "publicShares",jsonrpc::JSON_STRING, "ethKeyName",jsonrpc::JSON_STRING, "secretShare",jsonrpc::JSON_STRING,"t",jsonrpc::JSON_INTEGER, "n",jsonrpc::JSON_INTEGER, "index",jsonrpc::JSON_INTEGER, NULL), &AbstractStubServer::dkgVerificationV2I); this->bindAndAddMethod(jsonrpc::Procedure("createBLSPrivateKeyV2", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "blsKeyName",jsonrpc::JSON_STRING, "ethKeyName",jsonrpc::JSON_STRING, "polyName", jsonrpc::JSON_STRING, "secretShare",jsonrpc::JSON_STRING,"t", jsonrpc::JSON_INTEGER,"n",jsonrpc::JSON_INTEGER, NULL), &AbstractStubServer::createBLSPrivateKeyV2I); + + this->bindAndAddMethod(jsonrpc::Procedure("getDecryptionShare", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "blsKeyName",jsonrpc::JSON_STRING,"publicDecryptionValue",jsonrpc::JSON_STRING, NULL), &AbstractStubServer::getDecryptionShareI); } inline virtual void importBLSKeyShareI(const Json::Value &request, Json::Value &response) @@ -97,7 +99,7 @@ class AbstractStubServer : public jsonrpc::AbstractServer } inline virtual void getVerificationVectorI(const Json::Value &request, Json::Value &response) { - response = this->getVerificationVector(request["polyName"].asString(), request["t"].asInt(), request["n"].asInt()); + response = this->getVerificationVector(request["polyName"].asString(), request["t"].asInt()); } inline virtual void getSecretShareI(const Json::Value &request, Json::Value &response) { @@ -161,6 +163,11 @@ class AbstractStubServer : public jsonrpc::AbstractServer response = this->createBLSPrivateKeyV2(request["blsKeyName"].asString(), request["ethKeyName"].asString(), request["polyName"].asString(),request["secretShare"].asString(),request["t"].asInt(), request["n"].asInt()); } + inline virtual void getDecryptionShareI(const Json::Value &request, Json::Value &response) + { + response = this->getDecryptionShare(request["blsKeyName"].asString(), request["publicDecryptionValue"].asString()); + } + virtual Json::Value importBLSKeyShare(const std::string& keyShare, const std::string& keyShareName) = 0; virtual Json::Value blsSignMessageHash(const std::string& keyShareName, const std::string& messageHash, int t, int n ) = 0; virtual Json::Value importECDSAKey(const std::string& keyShare, const std::string& keyShareName) = 0; @@ -169,7 +176,7 @@ class AbstractStubServer : public jsonrpc::AbstractServer virtual Json::Value ecdsaSignMessageHash(int base, const std::string& keyName, const std::string& messageHash) = 0; virtual Json::Value generateDKGPoly(const std::string& polyName, int t) = 0; - virtual Json::Value getVerificationVector(const std::string& polyName, int t, int n) = 0; + virtual Json::Value getVerificationVector(const std::string& polyName, int t) = 0; virtual Json::Value getSecretShare(const std::string& polyName, const Json::Value& publicKeys, int t, int n) = 0; virtual Json::Value dkgVerification( const std::string& publicShares, const std::string& ethKeyName, const std::string& SecretShare, int t, int n, int index) = 0; virtual Json::Value createBLSPrivateKey(const std::string& blsKeyName, const std::string& ethKeyName, const std::string& polyName, const std::string& SecretShare, int t, int n) = 0; @@ -186,6 +193,8 @@ class AbstractStubServer : public jsonrpc::AbstractServer virtual Json::Value getSecretShareV2(const std::string& polyName, const Json::Value& publicKeys, int t, int n) = 0; virtual Json::Value dkgVerificationV2( const std::string& publicShares, const std::string& ethKeyName, const std::string& SecretShare, int t, int n, int index) = 0; virtual Json::Value createBLSPrivateKeyV2(const std::string& blsKeyName, const std::string& ethKeyName, const std::string& polyName, const std::string & SecretShare, int t, int n) = 0; + + virtual Json::Value getDecryptionShare(const std::string& KeyName, const std::string& publicDecryptionValue) = 0; }; #endif //JSONRPC_CPP_STUB_ABSTRACTSTUBSERVER_H_ diff --git a/common.h b/common.h index 32981919..37942b88 100644 --- a/common.h +++ b/common.h @@ -31,16 +31,20 @@ using namespace std; #include #include #include - #include #include #include - #include - +#include #include - #include +#include +#include +#include +#include +#include + + #include "secure_enclave/Verify.h" #include "InvalidStateException.h" #include "SGXException.h" @@ -98,8 +102,6 @@ inline int getValue() { //Note: this value is in KB! return result; } - - #define CHECK_STATE(_EXPRESSION_) \ if (!(_EXPRESSION_)) { \ auto __msg__ = std::string("State check failed::") + #_EXPRESSION_ + " " + std::string(__FILE__) + ":" + std::to_string(__LINE__); \ @@ -136,8 +138,6 @@ BOOST_THROW_EXCEPTION(runtime_error(__ERR_STRING__)); \ // Copy from libconsensus - - inline string exec( const char* cmd ) { CHECK_STATE( cmd ); std::array< char, 128 > buffer; @@ -162,5 +162,4 @@ extern uint64_t initTime; #define WRITE_LOCK(__X__) std::unique_lock __LOCK__(__X__); - #endif //SGXWALLET_COMMON_H diff --git a/docker/start.sh b/docker/start.sh index 20d735d0..cd8eeffe 100755 --- a/docker/start.sh +++ b/docker/start.sh @@ -47,6 +47,6 @@ sleep 5 ./testw.py else sleep 3 -./sgxwallet $1 $2 $3 $4 $5 +./sgxwallet $1 $2 $3 $4 $5 $6 fi diff --git a/docs/building.md b/docs/building.md index ba4fa312..e2549ed0 100644 --- a/docs/building.md +++ b/docs/building.md @@ -2,6 +2,25 @@ # Building SGX wallet from source +## Build and install Intel SGX SDK + +We are currently using SGX SDK version 2.13. + +Below is a sequence of commands that builds SDK and installs it into /opt/intel directory. + + +```bash +git clone -b sgx_2.13 --depth 1 https://github.com/intel/linux-sgx +cd linux-sgx +make preparation +sudo make sdk_install_pkg_no_mitigation +cd /opt/intel +sudo sh -c 'echo yes | /linux-sgx/linux/installer/bin/sgx_linux_x64_sdk_*.bin +sudo make psw_install_pkg +sudo cp /linux-sgx/linux/installer/bin/sgx_linux_x64_psw*.bin . +sudo ./sgx_linux_x64_psw*.bin --no-start-aesm +``` + ## Clone this repository and its submodules `git clone --recurse-submodules https://github.com/skalenetwork/sgxwallet.git` @@ -23,7 +42,7 @@ cd scripts; ./build_deps.py; cd .. ## Set SGX environment variables ```bash -source sgx-sdk-build/sgxsdk/environment +source /opt/intel/sgxsdk/environment ``` ## Configure and build sgxwallet @@ -40,6 +59,7 @@ make Note: to run in simulation mode, add --enable-sgx-simulation flag when you run configure. ```bash +./autoconf.bash ./configure --enable-sgx-simulation make ``` diff --git a/docs/run-in-hardware-mode.md b/docs/run-in-hardware-mode.md index 0f7c4e68..07fe9e18 100644 --- a/docs/run-in-hardware-mode.md +++ b/docs/run-in-hardware-mode.md @@ -52,6 +52,8 @@ sgxwallet operates on the following network ports: - 1027 (http for initial SSL certification signing) - 1028 (localhost for admin ) - 1029 (http only operation) +- 1030 (localhost for informational requests) +- 1031 (zmq) If operating with a firewall, please make sure these ports are open so clients are able to connect to the server. @@ -62,10 +64,11 @@ If operating with a firewall, please make sure these ports are open so clients a - \-s Sign client certificate without human confirmation - \-d Turn on debug output - \-v Verbose mode: turn on debug output -- \-vv Detailed verbose mode: turn on debug and trace outputs +- \-V Detailed verbose mode: turn on debug and trace outputs - \-n Launch SGXWalletServer using http (not https) - \-b Restore from back up (you will need to enter backup key) - \-y Do not ask user to acknowledge receipt of backup key +- \-e Check whether one who is trying to access the key is the same user who created it (Ownership is checked via SSL certificate for now. Deleting old SSL certificates and trying to access the keys created before will cause the error!) - \-T Generate test keys ### Healthcheck diff --git a/run_sgx/docker-compose.yml b/run_sgx/docker-compose.yml index 43691a96..a8430f8d 100644 --- a/run_sgx/docker-compose.yml +++ b/run_sgx/docker-compose.yml @@ -21,7 +21,7 @@ services: options: max-size: "10m" max-file: "4" - command: -s -y -d + command: -s -y -d -e healthcheck: test: ["CMD", "ls", "/dev/isgx", "/dev/mei0"] diff --git a/run_sgx_sim/docker-compose.yml b/run_sgx_sim/docker-compose.yml index dd4ef5bc..aaeddba0 100644 --- a/run_sgx_sim/docker-compose.yml +++ b/run_sgx_sim/docker-compose.yml @@ -18,4 +18,4 @@ services: options: max-size: "10m" max-file: "4" - command: -s -y + command: -s -y -e diff --git a/scripts/build_deps.py b/scripts/build_deps.py index 2df8b310..2e204583 100755 --- a/scripts/build_deps.py +++ b/scripts/build_deps.py @@ -18,7 +18,7 @@ # You should have received a copy of the GNU Affero General Public License # along with sgxwallet. If not, see . # -# @file build_deps.py +# @file build_deps.py # @author Stan Kladko # @date 2018 # @@ -36,8 +36,6 @@ 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_BUILD_DIR = LEVELDB_DIR + "/build" @@ -52,27 +50,20 @@ print("Cleaning") -subprocess.call(["rm", "-f", "install-sh"]) -subprocess.call(["rm", "-f", "compile"]) -subprocess.call(["rm", "-f", "missing"]) -subprocess.call(["rm", "-f", "depcomp"]) -subprocess.call(["rm", "-rf", GMP_BUILD_DIR]) +subprocess.call(["rm", "-f", "install-sh"]) +subprocess.call(["rm", "-f", "compile"]) +subprocess.call(["rm", "-f", "missing"]) +subprocess.call(["rm", "-f", "depcomp"]) +subprocess.call(["rm", "-rf", GMP_BUILD_DIR]) subprocess.call(["rm", "-rf", TGMP_BUILD_DIR]) subprocess.call(["rm", "-rf", SDK_DIR]) -subprocess.call(["rm", "-rf", GMP_BUILD_DIR]) +subprocess.call(["rm", "-rf", GMP_BUILD_DIR]) subprocess.call(["rm", "-rf", TGMP_BUILD_DIR]) subprocess.call(["rm", "-rf", SDK_DIR]) - - - - - - assert subprocess.call(["cp", "configure.gmp", GMP_DIR + "/configure"]) == 0 - print("Build LibBLS"); os.chdir(BLS_DIR + "/deps") assert subprocess.call(["bash", "-c", "./build.sh"]) == 0 @@ -81,7 +72,6 @@ os.chdir(BLS_DIR + "/build") assert subprocess.call(["bash", "-c", "make"]) == 0 - print("Build ZMQ"); os.chdir(ZMQ_DIR) @@ -89,7 +79,6 @@ 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) diff --git a/scripts/install_packages.sh b/scripts/install_packages.sh index d7ebef70..3354434e 100755 --- a/scripts/install_packages.sh +++ b/scripts/install_packages.sh @@ -1,4 +1,5 @@ #!/bin/bash +sudo apt update sudo apt install -y build-essential make gcc g++ yasm python libprotobuf10 flex bison automake sudo apt install -y ccache cmake ccache autoconf texinfo libgcrypt20-dev libgnutls28-dev libtool pkg-config -sudo apt install -y ocaml ocaml-build \ No newline at end of file +sudo apt install -y ocaml ocamlbuild diff --git a/secure_enclave/AESUtils.c b/secure_enclave/AESUtils.c index e9be1d93..10bcce25 100644 --- a/secure_enclave/AESUtils.c +++ b/secure_enclave/AESUtils.c @@ -21,18 +21,15 @@ @date 2020 */ - #include "sgx_trts.h" #include "sgx_tcrypto.h" #include "stdlib.h" #include - #include "AESUtils.h" sgx_aes_gcm_128bit_key_t AES_key[1024]; - #define SAFE_CHAR_BUF(__X__, __Y__) ;char __X__ [ __Y__ ]; memset(__X__, 0, __Y__); int AES_encrypt(char *message, uint8_t *encr_message, uint64_t encrBufLen, unsigned char type, @@ -115,6 +112,11 @@ int AES_decrypt(uint8_t *encr_message, uint64_t length, char *message, uint64_t return -5; } + if (length < SGX_AESGCM_MAC_SIZE + SGX_AESGCM_IV_SIZE) { + LOG_ERROR("length < SGX_AESGCM_MAC_SIZE - SGX_AESGCM_IV_SIZE"); + return -5; + } + uint64_t len = length - SGX_AESGCM_MAC_SIZE - SGX_AESGCM_IV_SIZE; if (msgLen < len) { diff --git a/secure_enclave/AESUtils.h b/secure_enclave/AESUtils.h index d57e5a95..337f2ec1 100644 --- a/secure_enclave/AESUtils.h +++ b/secure_enclave/AESUtils.h @@ -32,7 +32,6 @@ int AES_decrypt(uint8_t *encr_message, uint64_t length, char *message, uint64_t uint8_t *type, uint8_t* exportable) ; - #define ECDSA '1' #define BLS '2' #define DKG '3' @@ -40,6 +39,4 @@ int AES_decrypt(uint8_t *encr_message, uint64_t length, char *message, uint64_t #define EXPORTABLE '1' #define NON_EXPORTABLE '2' - - #endif //SGXD_AESUTILS_H diff --git a/secure_enclave/DKGUtils.h b/secure_enclave/DKGUtils.h index d5d57bad..95565d86 100644 --- a/secure_enclave/DKGUtils.h +++ b/secure_enclave/DKGUtils.h @@ -14,7 +14,7 @@ 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 . + along with sgxwallet. If not, see . @file DKGUtils.h @author Stan Kladko @@ -45,14 +45,12 @@ EXTERNC void calc_secret_shares(const char* decrypted_coeffs, char * secret_shar EXTERNC int calc_secret_share(const char* decrypted_coeffs, char * s_share, unsigned _t, unsigned _n, unsigned ind); -EXTERNC int calc_public_shares(const char* decrypted_coeffs, char * public_shares, - unsigned _t); +EXTERNC int calc_public_shares(const char* decrypted_coeffs, char * public_shares, unsigned _t); EXTERNC int Verification ( char * public_shares, mpz_t decr_secret_share, int _t, int ind); EXTERNC int calc_bls_public_key(char* skey, char* pub_key); - EXTERNC int calc_secret_shareG2(const char* s_share, char * s_shareG2); #endif diff --git a/secure_enclave/EnclaveCommon.cpp b/secure_enclave/EnclaveCommon.cpp index cd3c755b..251d5d11 100644 --- a/secure_enclave/EnclaveCommon.cpp +++ b/secure_enclave/EnclaveCommon.cpp @@ -171,7 +171,6 @@ void enclave_init() { return; inited = 1; - LOG_INFO("Initing libff"); try { diff --git a/secure_enclave/Makefile.am b/secure_enclave/Makefile.am index a69d48bb..d81e848b 100644 --- a/secure_enclave/Makefile.am +++ b/secure_enclave/Makefile.am @@ -84,7 +84,7 @@ CLEANFILES+= secure_enclave_t.c secure_enclave_t.h secure_enclave_SOURCES = secure_enclave_t.c secure_enclave_t.h \ secure_enclave.c \ Curves.c NumberTheory.c Point.c Signature.c DHDkg.c AESUtils.c \ - DKGUtils.cpp EnclaveCommon.cpp DomainParameters.cpp ../third_party/SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_init.cpp \ + DKGUtils.cpp TEUtils.cpp EnclaveCommon.cpp DomainParameters.cpp ../third_party/SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_init.cpp \ ../third_party/SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_g2.cpp \ ../third_party/SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_g1.cpp $(ENCLAVE_KEY) $(ENCLAVE_CONFIG) @@ -113,7 +113,7 @@ secure_enclave_LDADD = @SGX_ENCLAVE_LDADD@ ## --startgroup and --endgroup flags. (This would be where you'd add ## SGXSSL libraries, and your trusted c++ library -SGX_EXTRA_TLIBS=-lsgx_tgmp -lsgx_tservice -lsgx_urts -lsgx_tcxx +SGX_EXTRA_TLIBS=-lsgx_tgmp -lsgx_tservice -lsgx_urts -lsgx_tcxx diff --git a/secure_enclave/Makefile.in b/secure_enclave/Makefile.in index 4031e17f..af926874 100644 --- a/secure_enclave/Makefile.in +++ b/secure_enclave/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.15.1 from Makefile.am. +# Makefile.in generated by automake 1.16.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2017 Free Software Foundation, Inc. +# Copyright (C) 1994-2018 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -110,9 +110,10 @@ am_secure_enclave_OBJECTS = secure_enclave_t.$(OBJEXT) \ secure_enclave.$(OBJEXT) Curves.$(OBJEXT) \ NumberTheory.$(OBJEXT) Point.$(OBJEXT) Signature.$(OBJEXT) \ DHDkg.$(OBJEXT) AESUtils.$(OBJEXT) DKGUtils.$(OBJEXT) \ - EnclaveCommon.$(OBJEXT) DomainParameters.$(OBJEXT) \ - alt_bn128_init.$(OBJEXT) alt_bn128_g2.$(OBJEXT) \ - alt_bn128_g1.$(OBJEXT) $(am__objects_1) $(am__objects_1) + TEUtils.$(OBJEXT) EnclaveCommon.$(OBJEXT) \ + DomainParameters.$(OBJEXT) alt_bn128_init.$(OBJEXT) \ + alt_bn128_g2.$(OBJEXT) alt_bn128_g1.$(OBJEXT) $(am__objects_1) \ + $(am__objects_1) secure_enclave_OBJECTS = $(am_secure_enclave_OBJECTS) secure_enclave_DEPENDENCIES = @ENCLAVE_RELEASE_SIGN_FALSE@nodist_signed_enclave_debug_OBJECTS = \ @@ -137,7 +138,17 @@ am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ depcomp = $(SHELL) $(top_srcdir)/depcomp -am__depfiles_maybe = depfiles +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/AESUtils.Po ./$(DEPDIR)/Curves.Po \ + ./$(DEPDIR)/DHDkg.Po ./$(DEPDIR)/DKGUtils.Po \ + ./$(DEPDIR)/DomainParameters.Po ./$(DEPDIR)/EnclaveCommon.Po \ + ./$(DEPDIR)/NumberTheory.Po ./$(DEPDIR)/Point.Po \ + ./$(DEPDIR)/Signature.Po ./$(DEPDIR)/TEUtils.Po \ + ./$(DEPDIR)/alt_bn128_g1.Po ./$(DEPDIR)/alt_bn128_g2.Po \ + ./$(DEPDIR)/alt_bn128_init.Po ./$(DEPDIR)/secure_enclave.Po \ + ./$(DEPDIR)/secure_enclave_t.Po \ + ./$(DEPDIR)/signed_enclave_debug.Po \ + ./$(DEPDIR)/signed_enclave_rel.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -338,12 +349,12 @@ ENCLAVE_KEY = test_insecure_private_key.pem #$(ENCLAVE)_private.pem secure_enclave_SOURCES = secure_enclave_t.c secure_enclave_t.h \ secure_enclave.c \ Curves.c NumberTheory.c Point.c Signature.c DHDkg.c AESUtils.c \ - DKGUtils.cpp EnclaveCommon.cpp DomainParameters.cpp ../third_party/SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_init.cpp \ + DKGUtils.cpp TEUtils.cpp EnclaveCommon.cpp DomainParameters.cpp ../third_party/SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_init.cpp \ ../third_party/SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_g2.cpp \ ../third_party/SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_g1.cpp $(ENCLAVE_KEY) $(ENCLAVE_CONFIG) secure_enclave_LDADD = @SGX_ENCLAVE_LDADD@ -SGX_EXTRA_TLIBS = -lsgx_tgmp -lsgx_tservice -lsgx_urts -lsgx_tcxx +SGX_EXTRA_TLIBS = -lsgx_tgmp -lsgx_tservice -lsgx_urts -lsgx_tcxx all: all-am .SUFFIXES: @@ -365,8 +376,8 @@ Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_srcdir)/build-aux/sgx_enclave.am $(am__empty): @@ -431,22 +442,29 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/AESUtils.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Curves.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DHDkg.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DKGUtils.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DomainParameters.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/EnclaveCommon.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NumberTheory.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Point.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Signature.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alt_bn128_g1.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alt_bn128_g2.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alt_bn128_init.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/secure_enclave.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/secure_enclave_t.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signed_enclave_debug.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signed_enclave_rel.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/AESUtils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Curves.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DHDkg.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DKGUtils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DomainParameters.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/EnclaveCommon.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NumberTheory.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Point.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Signature.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TEUtils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alt_bn128_g1.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alt_bn128_g2.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alt_bn128_init.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/secure_enclave.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/secure_enclave_t.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signed_enclave_debug.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signed_enclave_rel.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @@ -570,7 +588,10 @@ cscopelist-am: $(am__tagged_files) distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -distdir: $(DISTFILES) +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ @@ -643,7 +664,23 @@ clean: clean-am clean-am: clean-generic clean-libexecPROGRAMS mostlyclean-am distclean: distclean-am - -rm -rf ./$(DEPDIR) + -rm -f ./$(DEPDIR)/AESUtils.Po + -rm -f ./$(DEPDIR)/Curves.Po + -rm -f ./$(DEPDIR)/DHDkg.Po + -rm -f ./$(DEPDIR)/DKGUtils.Po + -rm -f ./$(DEPDIR)/DomainParameters.Po + -rm -f ./$(DEPDIR)/EnclaveCommon.Po + -rm -f ./$(DEPDIR)/NumberTheory.Po + -rm -f ./$(DEPDIR)/Point.Po + -rm -f ./$(DEPDIR)/Signature.Po + -rm -f ./$(DEPDIR)/TEUtils.Po + -rm -f ./$(DEPDIR)/alt_bn128_g1.Po + -rm -f ./$(DEPDIR)/alt_bn128_g2.Po + -rm -f ./$(DEPDIR)/alt_bn128_init.Po + -rm -f ./$(DEPDIR)/secure_enclave.Po + -rm -f ./$(DEPDIR)/secure_enclave_t.Po + -rm -f ./$(DEPDIR)/signed_enclave_debug.Po + -rm -f ./$(DEPDIR)/signed_enclave_rel.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags @@ -689,7 +726,23 @@ install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am - -rm -rf ./$(DEPDIR) + -rm -f ./$(DEPDIR)/AESUtils.Po + -rm -f ./$(DEPDIR)/Curves.Po + -rm -f ./$(DEPDIR)/DHDkg.Po + -rm -f ./$(DEPDIR)/DKGUtils.Po + -rm -f ./$(DEPDIR)/DomainParameters.Po + -rm -f ./$(DEPDIR)/EnclaveCommon.Po + -rm -f ./$(DEPDIR)/NumberTheory.Po + -rm -f ./$(DEPDIR)/Point.Po + -rm -f ./$(DEPDIR)/Signature.Po + -rm -f ./$(DEPDIR)/TEUtils.Po + -rm -f ./$(DEPDIR)/alt_bn128_g1.Po + -rm -f ./$(DEPDIR)/alt_bn128_g2.Po + -rm -f ./$(DEPDIR)/alt_bn128_init.Po + -rm -f ./$(DEPDIR)/secure_enclave.Po + -rm -f ./$(DEPDIR)/secure_enclave_t.Po + -rm -f ./$(DEPDIR)/signed_enclave_debug.Po + -rm -f ./$(DEPDIR)/signed_enclave_rel.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic @@ -709,19 +762,19 @@ uninstall-am: uninstall-libexecPROGRAMS .MAKE: install-am install-strip -.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ - clean-libexecPROGRAMS cscopelist-am ctags ctags-am distclean \ - distclean-compile distclean-generic distclean-tags distdir dvi \ - dvi-am html html-am info info-am install install-am \ - install-data install-data-am install-dvi install-dvi-am \ - install-exec install-exec-am install-html install-html-am \ - install-info install-info-am install-libexecPROGRAMS \ - install-man install-pdf install-pdf-am install-ps \ - install-ps-am install-strip installcheck installcheck-am \ - installdirs maintainer-clean maintainer-clean-generic \ - mostlyclean mostlyclean-compile mostlyclean-generic pdf pdf-am \ - ps ps-am tags tags-am uninstall uninstall-am \ - uninstall-libexecPROGRAMS +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libexecPROGRAMS cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am \ + install-libexecPROGRAMS install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-libexecPROGRAMS .PRECIOUS: Makefile diff --git a/secure_enclave/TEUtils.cpp b/secure_enclave/TEUtils.cpp new file mode 100644 index 00000000..6947f3c9 --- /dev/null +++ b/secure_enclave/TEUtils.cpp @@ -0,0 +1,211 @@ +/* + Copyright (C) 2021-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 . + + @file TEUtils.cpp + @author Oleh Nikolaiev + @date 2021 +*/ + +#ifndef SGXWALLET_DKGUTILS_H +#define SGXWALLET_DKGUTILS_H + +#ifdef __cplusplus +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +#ifdef USER_SPACE + +#include +#else +#include <../tgmp-build/include/sgx_tgmp.h> +#endif + +#include +#include +#include +#include + +#include <../SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_pp.hpp> +#include <../SCIPR/libff/algebra/fields/fp.hpp> + +#include <../SCIPR/libff/algebra/curves/alt_bn128/alt_bn128_g2.hpp> + +#include "EnclaveConstants.h" +#include "EnclaveCommon.h" +#include "TEUtils.h" + +template +std::string fieldElementToString(const T &field_elem, int base = 10) { + + std::string ret; + + mpz_t t; + mpz_init(t); + + try { + + field_elem.as_bigint().to_mpz(t); + + SAFE_CHAR_BUF(arr, BUF_LEN); + + char *tmp = mpz_get_str(arr, base, t); + + ret = std::string(tmp); + + goto clean; + + } catch (std::exception &e) { + LOG_ERROR(e.what()); + goto clean; + } catch (...) { + LOG_ERROR("Unknown throwable"); + goto clean; + } + + clean: + mpz_clear(t); + return ret; +} + +std::string ConvertG2ElementToString(const libff::alt_bn128_G2 &elem, int base = 10, const std::string &delim = ":") { + + std::string result = ""; + + try { + + result += fieldElementToString(elem.X.c0); + result += delim; + result += fieldElementToString(elem.X.c1); + result += delim; + result += fieldElementToString(elem.Y.c0); + result += delim; + result += fieldElementToString(elem.Y.c1); + + return result; + + } catch (std::exception &e) { + LOG_ERROR(e.what()); + return result; + } catch (...) { + LOG_ERROR("Unknown throwable"); + return result; + } + + return result; +} + +std::vector SplitStringToFq(const char *coords, const char symbol) { + std::vector result; + std::string str(coords); + std::string delim; + + CHECK_ARG_CLEAN(coords); + + try { + + delim.push_back(symbol); + + size_t prev = 0, pos = 0; + do { + pos = str.find(delim, prev); + if (pos == std::string::npos) pos = str.length(); + std::string token = str.substr(prev, pos - prev); + if (!token.empty()) { + libff::alt_bn128_Fq coeff(token.c_str()); + result.push_back(coeff); + } + prev = pos + delim.length(); + } while (pos < str.length() && prev < str.length()); + + return result; + + } catch (std::exception &e) { + LOG_ERROR(e.what()); + return result; + } catch (...) { + LOG_ERROR("Unknown throwable"); + return result; + } + + clean: + return result; +} + +EXTERNC int getDecryptionShare(char* skey_hex, char* decryptionValue, char* decryption_share) { + mpz_t skey; + mpz_init(skey); + + int ret = 1; + + CHECK_ARG_CLEAN(skey_hex); + CHECK_ARG_CLEAN(decryptionValue); + CHECK_ARG_CLEAN(decryption_share); + + try { + if (mpz_set_str(skey, skey_hex, 16) == -1) { + mpz_clear(skey); + return 1; + } + + char skey_dec[mpz_sizeinbase(skey, 10) + 2]; + mpz_get_str(skey_dec, 10, skey); + + libff::alt_bn128_Fr bls_skey(skey_dec); + + auto splitted_decryption_value = SplitStringToFq(decryptionValue, ':'); + + libff::alt_bn128_G2 decryption_value; + decryption_value.Z = libff::alt_bn128_Fq2::one(); + + decryption_value.X.c0 = splitted_decryption_value[0]; + decryption_value.X.c1 = splitted_decryption_value[1]; + decryption_value.Y.c0 = splitted_decryption_value[2]; + decryption_value.Y.c1 = splitted_decryption_value[3]; + + if ( !decryption_value.is_well_formed() ) { + mpz_clear(skey); + return 1; + } + + libff::alt_bn128_G2 decryption_share_point = bls_skey * decryption_value; + decryption_share_point.to_affine_coordinates(); + + std::string result = ConvertG2ElementToString(decryption_share_point); + + strncpy(decryption_share, result.c_str(), result.length()); + + mpz_clear(skey); + + return 0; + + } catch (std::exception &e) { + LOG_ERROR(e.what()); + return 1; + } catch (...) { + LOG_ERROR("Unknown throwable"); + return 1; + } + + clean: + mpz_clear(skey); + return ret; +} + +#endif diff --git a/secure_enclave/TEUtils.h b/secure_enclave/TEUtils.h new file mode 100644 index 00000000..0e5c5442 --- /dev/null +++ b/secure_enclave/TEUtils.h @@ -0,0 +1,42 @@ +/* + Copyright (C) 2021-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 . + + @file TEUtils.h + @author Oleh Nikolaiev + @date 2021 +*/ + +#ifndef SGXWALLET_DKGUTILS_H +#define SGXWALLET_DKGUTILS_H + +#ifdef __cplusplus +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +#ifdef USER_SPACE + +#include +#else +#include <../tgmp-build/include/sgx_tgmp.h> +#endif + +EXTERNC int getDecryptionShare(char* secret, char* decryptionValue, char* decryption_share); + +#endif diff --git a/secure_enclave/secure_enclave.c b/secure_enclave/secure_enclave.c index f7d28a3a..c65bcb94 100644 --- a/secure_enclave/secure_enclave.c +++ b/secure_enclave/secure_enclave.c @@ -54,6 +54,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Curves.h" #include "DHDkg.h" #include "AESUtils.h" +#include "TEUtils.h" #include "EnclaveConstants.h" #include "EnclaveCommon.h" @@ -66,7 +67,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define INIT_ERROR_STATE *errString = 0; *errStatus = UNKNOWN_ERROR; #define SET_SUCCESS *errStatus = 0; - #define CHECK_STATE(_EXPRESSION_) \ if (!(_EXPRESSION_)) { \ LOG_ERROR("State check failed::");LOG_ERROR(#_EXPRESSION_); \ @@ -91,7 +91,6 @@ LOG_ERROR(errString); \ goto clean; \ }; - #define CHECK_STATUS2(__ERRMESSAGE__) if (status != SGX_SUCCESS) { \ snprintf(errString, BUF_LEN, __ERRMESSAGE__, status); \ LOG_ERROR(errString); \ @@ -138,10 +137,8 @@ void trustedEnclaveInit(uint64_t _logLevel) { LOG_INFO("Calling enclave init"); - enclave_init(); - LOG_INFO("Reading random"); globalRandom = calloc(32,1); @@ -228,7 +225,6 @@ void get_global_random(unsigned char *_randBuff, uint64_t _size) { memcpy(_randBuff, globalRandom, _size); } - void sealHexSEK(int *errStatus, char *errString, uint8_t *encrypted_sek, uint64_t *enc_len, char *sek_hex) { CALL_ONCE @@ -601,7 +597,6 @@ void trustedEcdsaSign(int *errStatus, char *errString, uint8_t *encryptedPrivate LOG_DEBUG("SGX call completed"); } - void trustedDecryptKey(int *errStatus, char *errString, uint8_t *encryptedPrivateKey, uint64_t enc_len, char *key) { LOG_DEBUG(__FUNCTION__); @@ -648,7 +643,6 @@ void trustedDecryptKey(int *errStatus, char *errString, uint8_t *encryptedPrivat ; } - void trustedEncryptKey(int *errStatus, char *errString, const char *key, uint8_t *encryptedPrivateKey, uint64_t *enc_len) { LOG_INFO(__FUNCTION__); @@ -999,14 +993,14 @@ void trustedGetEncryptedSecretShareV2(int *errStatus, char *errString, void trustedGetPublicShares(int *errStatus, char *errString, uint8_t *encrypted_dkg_secret, uint64_t enc_len, char *public_shares, - unsigned _t, unsigned _n) { + unsigned _t) { LOG_INFO(__FUNCTION__); INIT_ERROR_STATE CHECK_STATE(encrypted_dkg_secret); CHECK_STATE(public_shares); - CHECK_STATE(_t <= _n && _n > 0) + CHECK_STATE(_t > 0) SAFE_CHAR_BUF(decrypted_dkg_secret, DKG_MAX_SEALED_LEN); @@ -1380,3 +1374,35 @@ trustedGetBlsPubKey(int *errStatus, char *errString, uint8_t *encryptedPrivateKe clean: ; } + +void trustedGetDecryptionShare( int *errStatus, char* errString, uint8_t* encryptedPrivateKey, + const char* public_decryption_value, uint64_t key_len, + char* decryption_share ) { + LOG_DEBUG(__FUNCTION__); + + INIT_ERROR_STATE + + CHECK_STATE(decryption_share); + CHECK_STATE(encryptedPrivateKey); + + SAFE_CHAR_BUF(skey_hex, BUF_LEN); + + uint8_t type = 0; + uint8_t exportable = 0; + + int status = AES_decrypt(encryptedPrivateKey, key_len, skey_hex, BUF_LEN, + &type, &exportable); + + CHECK_STATUS2("AES decrypt failed %d"); + + skey_hex[ECDSA_SKEY_LEN - 1] = 0; + + status = getDecryptionShare(skey_hex, public_decryption_value, decryption_share); + + CHECK_STATUS("could not calculate decryption share"); + + SET_SUCCESS + + clean: + ; +} diff --git a/secure_enclave/secure_enclave.edl b/secure_enclave/secure_enclave.edl index 6ab5ad10..82cf6e64 100644 --- a/secure_enclave/secure_enclave.edl +++ b/secure_enclave/secure_enclave.edl @@ -122,8 +122,7 @@ enclave { [in, count = 3050] uint8_t* encrypted_dkg_secret, uint64_t enc_len, [out, count = 10000] char* public_shares, - unsigned _t, - unsigned _n); + unsigned _t); public void trustedDkgVerify( [out] int *errStatus, @@ -176,10 +175,18 @@ enclave { public void trustedGetBlsPubKey( [out]int *errStatus, - [out, count = SMALL_BUF_SIZE] char* err_string, + [out, count = SMALL_BUF_SIZE] char* err_string, [in, count = SMALL_BUF_SIZE] uint8_t* encrypted_key, uint64_t key_len, [out, count = 320] char* bls_pub_key); + + public void trustedGetDecryptionShare( + [out]int *errStatus, + [out, count = SMALL_BUF_SIZE] char* err_string, + [in, count = SMALL_BUF_SIZE] uint8_t* encrypted_key, + [in, count = 320] const char* public_decryption_value, + uint64_t key_len, + [out, count = 320] char* decrption_share); }; untrusted { diff --git a/sgxwall.cpp b/sgxwall.cpp index 18c0c6be..42740c90 100644 --- a/sgxwall.cpp +++ b/sgxwall.cpp @@ -36,7 +36,7 @@ #include "TestUtils.h" -#include "ZMQServer.h" +#include "zmq_src/ZMQServer.h" #include "testw.h" #include "sgxwall.h" @@ -55,6 +55,7 @@ void SGXWallet::printUsage() { cerr << " -n Use http instead of https. Default is to use https with a selg-signed server cert. Insecure! \n"; cerr << " -c Disable client authentication using certificates. Insecure!\n"; cerr << " -s Sign client certificates without human confirmation. Insecure! \n"; + cerr << " -e Only owner of the key can access it.\n"; } @@ -100,6 +101,7 @@ int main(int argc, char *argv[]) { bool checkClientCertOption = true; bool autoSignClientCertOption = false; bool generateTestKeys = false; + bool checkKeyOwnership = false; std::signal(SIGABRT, SGXWallet::signalHandler); @@ -110,7 +112,7 @@ int main(int argc, char *argv[]) { exit(-21); } - while ((opt = getopt(argc, argv, "cshd0abyvVnT")) != -1) { + while ((opt = getopt(argc, argv, "cshd0abyvVneT")) != -1) { switch (opt) { case 'h': SGXWallet::printUsage(); @@ -136,7 +138,11 @@ int main(int argc, char *argv[]) { break; case 'n': useHTTPSOption = false; - break; + checkKeyOwnership = false; + break; + case 'e': + checkKeyOwnership = true; + break; case 'a': enterBackupKeyOption = false; break; @@ -179,10 +185,9 @@ int main(int argc, char *argv[]) { } cerr << "Calling initAll ..." << endl; - initAll(enclaveLogLevel, checkClientCertOption, checkClientCertOption, autoSignClientCertOption, generateTestKeys); + initAll(enclaveLogLevel, checkClientCertOption, checkClientCertOption, autoSignClientCertOption, generateTestKeys, checkKeyOwnership); cerr << "Completed initAll." << endl; - //check if test keys already exist string TEST_KEYS_4_NODE = "sgx_data/4node.json"; @@ -194,7 +199,6 @@ int main(int argc, char *argv[]) { cerr << "Found test keys." << endl; } - if (generateTestKeys && !keysExist && !ExitHandler::shouldExit()) { cerr << "Generating test keys ..." << endl; @@ -221,8 +225,6 @@ int main(int argc, char *argv[]) { cerr << "Successfully completed generating test keys into sgx_data" << endl; } - - while ( !ExitHandler::shouldExit() ) { sleep(10); } diff --git a/sgxwallet_common.h b/sgxwallet_common.h index bc7c475d..cbc4bd43 100644 --- a/sgxwallet_common.h +++ b/sgxwallet_common.h @@ -184,6 +184,9 @@ extern bool autoconfirm; #define SGX_SERVER_FAILED_TO_START -111 #define CORRUPT_DATABASE -112 #define INVALID_SEK -113 +#define INVALID_DECRYPTION_VALUE_FORMAT -114 +#define INVALID_KEY_FORMAT -115 +#define KEY_ALREADY_REGISTERED -116 #define SGX_ENCLAVE_ERROR -666 diff --git a/stubclient.h b/stubclient.h index 2a379406..b2334ed5 100644 --- a/stubclient.h +++ b/stubclient.h @@ -98,11 +98,10 @@ class StubClient : public jsonrpc::Client throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); } - Json::Value getVerificationVector(const std::string& polyName, int t, int n) + Json::Value getVerificationVector(const std::string& polyName, int t) { Json::Value p; p["polyName"] = polyName; - p["n"] = n; p["t"] = t; Json::Value result = this->CallMethod("getVerificationVector",p); if (result.isObject()) @@ -215,6 +214,19 @@ class StubClient : public jsonrpc::Client throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); } + Json::Value getDecryptionShare(const std::string& blsKeyName, const std::string& publicDecryptionValue) + { + Json::Value p; + p["blsKeyName"] = blsKeyName; + p["publicDecryptionValue"] = publicDecryptionValue; + + Json::Value result = this->CallMethod("getDecryptionShare",p); + if (result.isObject()) + return result; + else + throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); + } + Json::Value calculateAllBLSPublicKeys(const Json::Value& publicShares, int t, int n) { Json::Value p; diff --git a/testw.cpp b/testw.cpp index 19bf886f..005791bb 100644 --- a/testw.cpp +++ b/testw.cpp @@ -14,7 +14,7 @@ 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 . + along with sgxwallet. If not, see . @file testw.cpp @author Stan Kladko @@ -37,6 +37,7 @@ #include #include "BLSCrypto.h" +#include "CryptoTools.h" #include "ServerInit.h" #include "DKGCrypto.h" #include "SGXException.h" @@ -57,15 +58,14 @@ #include "SGXRegistrationServer.h" #include "SGXWalletServer.h" -#include "ZMQClient.h" -#include "ZMQServer.h" +#include "zmq_src/ZMQClient.h" +#include "zmq_src/ZMQServer.h" #include "sgxwallet.h" #include "TestUtils.h" #include "testw.h" #define PRINT_SRC_LINE cerr << "Executing line " << to_string(__LINE__) << endl; - using namespace jsonrpc; using namespace std; @@ -74,7 +74,7 @@ class TestFixture { TestFixture() { TestUtils::resetDB(); setOptions(L_INFO, false, true); - initAll(L_INFO, false, false, true, false); + initAll(L_INFO, false, false, true, false, true); } ~TestFixture() { @@ -88,7 +88,7 @@ class TestFixtureHTTPS { TestFixtureHTTPS() { TestUtils::resetDB(); setOptions(L_INFO, true, true); - initAll(L_INFO, false, true, true, false); + initAll(L_INFO, false, true, true, false, true); } ~TestFixtureHTTPS() { @@ -97,13 +97,12 @@ class TestFixtureHTTPS { } }; - class TestFixtureZMQSign { public: TestFixtureZMQSign() { TestUtils::resetDB(); setOptions(L_INFO, false, true); - initAll(L_INFO, false, true, true, false); + initAll(L_INFO, false, true, true, false, false); } ~TestFixtureZMQSign() { @@ -112,12 +111,11 @@ class TestFixtureZMQSign { } }; - class TestFixtureNoResetFromBackup { public: TestFixtureNoResetFromBackup() { setFullOptions(L_INFO, false, true, true); - initAll(L_INFO, false, false, true, false); + initAll(L_INFO, false, false, true, false, true); } ~TestFixtureNoResetFromBackup() { @@ -127,12 +125,11 @@ class TestFixtureNoResetFromBackup { } }; - class TestFixtureNoReset { public: TestFixtureNoReset() { setOptions(L_INFO, false, true); - initAll(L_INFO, false, false, true, false); + initAll(L_INFO, false, false, true, false, true); } ~TestFixtureNoReset() { @@ -162,7 +159,6 @@ TEST_CASE_METHOD(TestFixture, "ECDSA AES keygen and signature test", "[ecdsa-aes vector signatureS(BUF_LEN, 0); uint8_t signatureV = 0; - for (int i = 0; i < 50; i++) { PRINT_SRC_LINE status = trustedEcdsaSign(eid, &errStatus, errMsg.data(), encrPrivKey.data(), encLen, @@ -175,7 +171,6 @@ TEST_CASE_METHOD(TestFixture, "ECDSA AES keygen and signature test", "[ecdsa-aes } - TEST_CASE_METHOD(TestFixture, "ECDSA AES key gen", "[ecdsa-aes-key-gen]") { vector errMsg(BUF_LEN, 0); int errStatus = 0; @@ -193,7 +188,6 @@ TEST_CASE_METHOD(TestFixture, "ECDSA AES key gen", "[ecdsa-aes-key-gen]") { REQUIRE(errStatus == SGX_SUCCESS); } - TEST_CASE_METHOD(TestFixture, "ECDSA AES get public key", "[ecdsa-aes-get-pub-key]") { int errStatus = 0; vector errMsg(BUF_LEN, 0); @@ -222,7 +216,6 @@ TEST_CASE_METHOD(TestFixture, "ECDSA AES get public key", "[ecdsa-aes-get-pub-ke REQUIRE(errStatus == SGX_SUCCESS); } - /* Do later TEST_CASE_METHOD("BLS key encrypt/decrypt", "[bls-key-encrypt-decrypt]") { resetDB(); @@ -247,14 +240,9 @@ TEST_CASE_METHOD("BLS key encrypt/decrypt", "[bls-key-encrypt-decrypt]") { printf("Decrypted key len %d\n", (int) strlen(plaintextKey)); printf("Decrypted key: %s\n", plaintextKey); free(plaintextKey); - - - } - */ - string genECDSAKeyAPI(StubClient &_c) { Json::Value genKey = _c.generateECDSAKey(); CHECK_STATE(genKey["status"].asInt() == 0); @@ -284,10 +272,8 @@ TEST_CASE_METHOD(TestFixture, "ECDSA key gen API", "[ecdsa-key-gen-api]") { auto keyName = genECDSAKeyAPI(c); - Json::Value sig = c.ecdsaSignMessageHash(10, keyName, SAMPLE_HASH); - for (int i = 0; i <= 20; i++) { try { PRINT_SRC_LINE @@ -311,7 +297,6 @@ TEST_CASE_METHOD(TestFixture, "BLS key encrypt", "[bls-key-encrypt]") { sleep(3); } - TEST_CASE_METHOD(TestFixture, "DKG AES gen test", "[dkg-aes-gen]") { vector encryptedDKGSecret(BUF_LEN, 0); vector errMsg(BUF_LEN, 0); @@ -336,7 +321,6 @@ TEST_CASE_METHOD(TestFixture, "DKG AES gen test", "[dkg-aes-gen]") { sleep(3); } - TEST_CASE_METHOD(TestFixture, "DKG AES public shares test", "[dkg-aes-pub-shares]") { vector encryptedDKGSecret(BUF_LEN, 0); vector errMsg(BUF_LEN, 0); @@ -356,7 +340,7 @@ TEST_CASE_METHOD(TestFixture, "DKG AES public shares test", "[dkg-aes-pub-shares vector pubShares(10000, 0); PRINT_SRC_LINE status = trustedGetPublicShares(eid, &errStatus, errMsg1.data(), - encryptedDKGSecret.data(), encLen, pubShares.data(), t, n); + encryptedDKGSecret.data(), encLen, pubShares.data(), t); REQUIRE(status == SGX_SUCCESS); REQUIRE(errStatus == SGX_SUCCESS); @@ -446,11 +430,8 @@ TEST_CASE_METHOD(TestFixture, "DKG AES encrypted secret shares version 2 test", REQUIRE(errStatus == SGX_SUCCESS); } - /* * ( "verification test", "[verify]" ) { - - char* pubshares = "0d72c21fc5a43452ad5f36699822309149ce6ce2cdce50dafa896e873f1b8ddd12f65a2e9c39c617a1f695f076b33b236b47ed773901fc2762f8b6f63277f5e30d7080be8e98c97f913d1920357f345dc0916c1fcb002b7beb060aa8b6b473a011bfafe9f8a5d8ea4c643ca4101e5119adbef5ae64f8dfb39cd10f1e69e31c591858d7eaca25b4c412fe909ca87ca7aadbf6d97d32d9b984e93d436f13d43ec31f40432cc750a64ac239cad6b8f78c1f1dd37427e4ff8c1cc4fe1c950fcbcec10ebfd79e0c19d0587adafe6db4f3c63ea9a329724a8804b63a9422e6898c0923209e828facf3a073254ec31af4231d999ba04eb5b7d1e0056d742a65b766f2f3"; char *sec_share = "11592366544581417165283270001305852351194685098958224535357729125789505948557"; mpz_t sshare; @@ -458,15 +439,8 @@ TEST_CASE_METHOD(TestFixture, "DKG AES encrypted secret shares version 2 test", mpz_set_str(sshare, "11592366544581417165283270001305852351194685098958224535357729125789505948557", 10); int result = Verification(pubshares, sshare, 2, 0); REQUIRE(result == 1); - - }*/ - - - - - TEST_CASE_METHOD(TestFixture, "DKG_BLS test", "[dkg-bls]") { HttpClient client(RPC_ENDPOINT); StubClient c(client, JSONRPC_CLIENT_V2); @@ -509,7 +483,6 @@ TEST_CASE_METHOD(TestFixture, "DKG_BLS V2 test", "[dkg-bls-v2]") { TestUtils::doDKGV2(c, 16, 5, ecdsaKeyNames, blsKeyNames, schainID, dkgID); } - TEST_CASE_METHOD(TestFixture, "DKG_BLS ZMQ test", "[dkgblszmq]") { HttpClient client(RPC_ENDPOINT); StubClient c(client, JSONRPC_CLIENT_V2); @@ -554,6 +527,24 @@ TEST_CASE_METHOD(TestFixture, "Delete Bls Key", "[delete-bls-key]") { REQUIRE(c.deleteBlsKey(name)["deleted"] == true); } +TEST_CASE_METHOD(TestFixture, "Delete Bls Key Zmq", "[delete-bls-key-zmq]") { + auto client = make_shared(ZMQ_IP, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", + "./sgx_data/cert_data/rootCA.key"); + + std::string name = "BLS_KEY:SCHAIN_ID:123456789:NODE_ID:0:DKG_ID:0"; + libff::alt_bn128_Fr key = libff::alt_bn128_Fr( + "6507625568967977077291849236396320012317305261598035438182864059942098934847"); + std::string key_str = TestUtils::stringFromFr(key); + REQUIRE(!client->importBLSKeyShare(key_str, name)); + + key_str = "0xe632f7fde2c90a073ec43eaa90dca7b82476bf28815450a11191484934b9c3f"; + REQUIRE(client->importBLSKeyShare(key_str, name)); + + REQUIRE_NOTHROW(client->blsSignMessageHash(name, SAMPLE_HASH, 1, 1)); + + REQUIRE(client->deleteBLSKey(name)); +} + TEST_CASE_METHOD(TestFixture, "Import ECDSA Key", "[import-ecdsa-key]") { HttpClient client(RPC_ENDPOINT); StubClient c(client, JSONRPC_CLIENT_V2); @@ -570,6 +561,21 @@ TEST_CASE_METHOD(TestFixture, "Import ECDSA Key", "[import-ecdsa-key]") { REQUIRE(c.ecdsaSignMessageHash(16, name, SAMPLE_HASH)["status"] == 0); } +TEST_CASE_METHOD(TestFixture, "Import ECDSA Key Zmq", "[import-ecdsa-key-zmq]") { + auto client = make_shared(ZMQ_IP, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", + "./sgx_data/cert_data/rootCA.key"); + + std::string name = "NEK:abcdef"; + REQUIRE_THROWS(client->importECDSAKey("6507625568967977077291849236396320012317305261598035438182864059942098934847", + name)); + + string key_str = "0xe632f7fde2c90a073ec43eaa90dca7b82476bf28815450a11191484934b9c3f"; + string response = client->importECDSAKey(key_str, name); + REQUIRE(response == client->getECDSAPublicKey(name)); + + REQUIRE_NOTHROW(client->ecdsaSignMessageHash(16, name, SAMPLE_HASH)); +} + TEST_CASE_METHOD(TestFixture, "Backup Key", "[backup-key]") { HttpClient client(RPC_ENDPOINT); StubClient c(client, JSONRPC_CLIENT_V2); @@ -590,6 +596,13 @@ TEST_CASE_METHOD(TestFixture, "Get ServerStatus", "[get-server-status]") { sleep(3); } +TEST_CASE_METHOD(TestFixture, "Get ServerStatusZmq", "[get-server-status-zmq]") { + auto client = make_shared(ZMQ_IP, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", + "./sgx_data/cert_data/rootCA.key"); + REQUIRE_NOTHROW(client->getServerStatus()); + sleep(3); +} + TEST_CASE_METHOD(TestFixture, "Get ServerVersion", "[get-server-version]") { HttpClient client(RPC_ENDPOINT); StubClient c(client, JSONRPC_CLIENT_V2); @@ -597,6 +610,12 @@ TEST_CASE_METHOD(TestFixture, "Get ServerVersion", "[get-server-version]") { sleep(3); } +TEST_CASE_METHOD(TestFixture, "Get ServerVersionZmq", "[get-server-version-zmq]") { + auto client = make_shared(ZMQ_IP, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", + "./sgx_data/cert_data/rootCA.key"); + REQUIRE(client->getServerVersion() == SGXWalletServer::getVersion()); + sleep(3); +} TEST_CASE_METHOD(TestFixtureHTTPS, "Cert request sign", "[cert-sign]") { @@ -620,15 +639,12 @@ TEST_CASE_METHOD(TestFixtureHTTPS, "Cert request sign", "[cert-sign]") { REQUIRE(result["status"] == 0); - PRINT_SRC_LINE result = SGXRegistrationServer::getServer()->SignCertificate("Haha"); REQUIRE(result["status"] != 0); } - - TEST_CASE_METHOD(TestFixture, "DKG API V2 test", "[dkg-api-v2]") { HttpClient client(RPC_ENDPOINT); StubClient c(client, JSONRPC_CLIENT_V2); @@ -647,7 +663,7 @@ TEST_CASE_METHOD(TestFixture, "DKG API V2 test", "[dkg-api-v2]") { Json::Value genPolyWrongName = c.generateDKGPoly("poly", 2); REQUIRE(genPolyWrongName["status"].asInt() != 0); - Json::Value verifVectWrongName = c.getVerificationVector("poly", 2, 2); + Json::Value verifVectWrongName = c.getVerificationVector("poly", 2); REQUIRE(verifVectWrongName["status"].asInt() != 0); Json::Value secretSharesWrongName = c.getSecretShareV2("poly", publicKeys, 2, 2); @@ -657,16 +673,12 @@ TEST_CASE_METHOD(TestFixture, "DKG API V2 test", "[dkg-api-v2]") { Json::Value genPolyWrong_t = c.generateDKGPoly(polyName, 33); REQUIRE(genPolyWrong_t["status"].asInt() != 0); - Json::Value verifVectWrong_t = c.getVerificationVector(polyName, 1, 2); + Json::Value verifVectWrong_t = c.getVerificationVector(polyName, 1); REQUIRE(verifVectWrong_t["status"].asInt() != 0); Json::Value secretSharesWrong_t = c.getSecretShareV2(polyName, publicKeys, 3, 3); REQUIRE(secretSharesWrong_t["status"].asInt() != 0); - // wrong_n - Json::Value verifVectWrong_n = c.getVerificationVector(polyName, 2, 1); - REQUIRE(verifVectWrong_n["status"].asInt() != 0); - Json::Value publicKeys1; publicKeys1.append(SAMPLE_DKG_PUB_KEY_1); Json::Value secretSharesWrong_n = c.getSecretShareV2(polyName, publicKeys1, 2, 1); @@ -681,14 +693,60 @@ TEST_CASE_METHOD(TestFixture, "DKG API V2 test", "[dkg-api-v2]") { REQUIRE_NOTHROW(c.getSecretShare(polyName, publicKeys, 2, 2)); REQUIRE(Skeys == c.getSecretShare(polyName, publicKeys, 2, 2)); - Json::Value verifVect = c.getVerificationVector(polyName, 2, 2); - REQUIRE_NOTHROW(c.getVerificationVector(polyName, 2, 2)); - REQUIRE(verifVect == c.getVerificationVector(polyName, 2, 2)); + Json::Value verifVect = c.getVerificationVector(polyName, 2); + REQUIRE_NOTHROW(c.getVerificationVector(polyName, 2)); + REQUIRE(verifVect == c.getVerificationVector(polyName, 2)); Json::Value verificationWrongSkeys = c.dkgVerificationV2("", "", "", 2, 2, 1); REQUIRE(verificationWrongSkeys["status"].asInt() != 0); } +TEST_CASE_METHOD(TestFixture, "DKG API V2 ZMQ test", "[dkg-api-v2-zmq]") { + auto client = make_shared(ZMQ_IP, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", + "./sgx_data/cert_data/rootCA.key"); + + string polyName = SAMPLE_POLY_NAME; + + PRINT_SRC_LINE + REQUIRE(client->generateDKGPoly(polyName, 2)); + + Json::Value publicKeys; + publicKeys.append(SAMPLE_DKG_PUB_KEY_1); + publicKeys.append(SAMPLE_DKG_PUB_KEY_2); + + // wrongName + REQUIRE(!client->generateDKGPoly("poly", 2)); + + REQUIRE_THROWS(client->getVerificationVector("poly", 2)); + + REQUIRE_THROWS(client->getSecretShare("poly", publicKeys, 2, 2)); + + // wrong_t + REQUIRE(!client->generateDKGPoly(polyName, 33)); + + REQUIRE_THROWS(client->getVerificationVector(polyName, 0)); + + REQUIRE_THROWS(client->getSecretShare(polyName, publicKeys, 3, 3)); + + Json::Value publicKeys1; + publicKeys1.append(SAMPLE_DKG_PUB_KEY_1); + REQUIRE_THROWS(client->getSecretShare(polyName, publicKeys1, 2, 1)); + + //wrong number of publicKeys + REQUIRE_THROWS(client->getSecretShare(polyName, publicKeys, 2, 3)); + + //wrong verif + string Skeys = client->getSecretShare(polyName, publicKeys, 2, 2); + REQUIRE_NOTHROW(client->getSecretShare(polyName, publicKeys, 2, 2)); + REQUIRE(Skeys == client->getSecretShare(polyName, publicKeys, 2, 2)); + + Json::Value verifVect = client->getVerificationVector(polyName, 2); + REQUIRE_NOTHROW(client->getVerificationVector(polyName, 2)); + REQUIRE(verifVect == client->getVerificationVector(polyName, 2)); + + REQUIRE_THROWS(client->dkgVerification("", "", "", 2, 2, 1)); +} + TEST_CASE_METHOD(TestFixture, "PolyExists test", "[dkg-poly-exists]") { HttpClient client(RPC_ENDPOINT); StubClient c(client, JSONRPC_CLIENT_V2); @@ -708,7 +766,19 @@ TEST_CASE_METHOD(TestFixture, "PolyExists test", "[dkg-poly-exists]") { REQUIRE(!polyDoesNotExist["IsExist"].asBool()); } +TEST_CASE_METHOD(TestFixture, "PolyExistsZmq test", "[dkg-poly-exists-zmq]") { + auto client = make_shared(ZMQ_IP, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", + "./sgx_data/cert_data/rootCA.key"); + + string polyName = SAMPLE_POLY_NAME; + REQUIRE_NOTHROW(client->generateDKGPoly(polyName, 2)); + + bool polyExists = client->isPolyExists(polyName); + REQUIRE(polyExists); + bool polyDoesNotExist = client->isPolyExists("Vasya"); + REQUIRE(!polyDoesNotExist); +} TEST_CASE_METHOD(TestFixture, "AES_DKG V2 test", "[aes-dkg-v2]") { HttpClient client(RPC_ENDPOINT); @@ -738,7 +808,7 @@ TEST_CASE_METHOD(TestFixture, "AES_DKG V2 test", "[aes-dkg-v2]") { polyNames[i] = polyName; PRINT_SRC_LINE - verifVects[i] = c.getVerificationVector(polyName, t, n); + verifVects[i] = c.getVerificationVector(polyName, t); REQUIRE(verifVects[i]["status"] == 0); pubEthKeys.append(ethKeys[i]["publicKey"]); @@ -834,8 +904,7 @@ TEST_CASE_METHOD(TestFixture, "AES_DKG V2 test", "[aes-dkg-v2]") { string hash = SAMPLE_HASH; - auto hash_arr = make_shared < array < uint8_t, - 32 > > (); + auto hash_arr = make_shared < array < uint8_t, 32 > > (); uint64_t binLen; @@ -849,8 +918,7 @@ TEST_CASE_METHOD(TestFixture, "AES_DKG V2 test", "[aes-dkg-v2]") { string endName = polyNames[i].substr(4); string blsName = "BLS_KEY" + polyNames[i].substr(4); auto response = c.createBLSPrivateKeyV2(blsName, ethKeys[i]["keyName"].asString(), polyNames[i], secShares[i], - t, - n); + t, n); REQUIRE(response["status"] == 0); PRINT_SRC_LINE @@ -882,6 +950,156 @@ TEST_CASE_METHOD(TestFixture, "AES_DKG V2 test", "[aes-dkg-v2]") { REQUIRE(common_public.VerifySigWithHelper(hash_arr, commonSig, t, n)); } +TEST_CASE_METHOD(TestFixture, "AES_DKG V2 ZMQ test", "[aes-dkg-v2-zmq]") { + auto client = make_shared(ZMQ_IP, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", + "./sgx_data/cert_data/rootCA.key"); + + int n = 2, t = 2; + vector ethKeys(n); + Json::Value verifVects[n]; + Json::Value pubEthKeys; + vector secretShares(n); + Json::Value pubBLSKeys[n]; + vector blsSigShares(n); + vector pubShares(n); + vector polyNames(n); + + int schainID = TestUtils::randGen(); + int dkgID = TestUtils::randGen(); + for (uint8_t i = 0; i < n; i++) { + auto generatedKey = client->generateECDSAKey(); + ethKeys[i] = generatedKey.second; + string polyName = + "POLY:SCHAIN_ID:" + to_string(schainID) + ":NODE_ID:" + to_string(i) + ":DKG_ID:" + to_string(dkgID); + CHECK_STATE(client->generateDKGPoly(polyName, t)); + polyNames[i] = polyName; + verifVects[i] = client->getVerificationVector(polyName, t); + + pubEthKeys.append(generatedKey.first); + } + + for (uint8_t i = 0; i < n; i++) { + secretShares[i] = client->getSecretShare(polyNames[i], pubEthKeys, t, n); + for (uint8_t k = 0; k < t; k++) { + for (uint8_t j = 0; j < 4; j++) { + string pubShare = verifVects[i][k][j].asString(); + pubShares[i] += TestUtils::convertDecToHex(pubShare); + } + } + } + + int k = 0; + vector secShares(n); + + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) { + string secretShare = secretShares[i].substr(192 * j, 192); + secShares[i] += secretShares[j].substr(192 * i, 192); + REQUIRE(client->dkgVerification(pubShares[i], ethKeys[j], secretShare, t, n, j)); + k++; + } + + auto complaintResponse = client->complaintResponse(polyNames[1], t, n, 0); + + string dhKey = std::get<0>(complaintResponse); + string shareG2 = std::get<1>(complaintResponse); + string secretShare = secretShares[1].substr(0, 192); + + vector message(65, 0); + + SAFE_CHAR_BUF(encr_sshare, BUF_LEN) + strncpy(encr_sshare, pubEthKeys[0].asString().c_str(), 128); + + SAFE_CHAR_BUF(common_key, BUF_LEN); + REQUIRE(sessionKeyRecoverDH(dhKey.c_str(), encr_sshare, common_key) == 0); + + uint8_t key_to_hash[33]; + uint64_t len; + REQUIRE( hex2carray(common_key, &len, key_to_hash, 64) ); + + auto hashed_key = cryptlite::sha256::hash_hex(string((char*)key_to_hash, 32)); + + SAFE_CHAR_BUF(derived_key, 33) + + uint64_t key_length; + REQUIRE(hex2carray(&hashed_key[0], &key_length, (uint8_t *) derived_key, 33)); + + SAFE_CHAR_BUF(encr_sshare_check, BUF_LEN) + strncpy(encr_sshare_check, secretShare.c_str(), ECDSA_SKEY_LEN - 1); + + REQUIRE(xorDecryptDHV2(derived_key, encr_sshare_check, message) == 0); + + mpz_t hex_share; + mpz_init(hex_share); + mpz_set_str(hex_share, message.data(), 16); + + libff::alt_bn128_Fr share(hex_share); + libff::alt_bn128_G2 decrypted_share_G2 = share * libff::alt_bn128_G2::one(); + decrypted_share_G2.to_affine_coordinates(); + + mpz_clear(hex_share); + + REQUIRE(convertG2ToString(decrypted_share_G2) == shareG2); + + Json::Value verificationVectorMult = std::get<2>(complaintResponse); + + libff::alt_bn128_G2 verificationValue = libff::alt_bn128_G2::zero(); + for (int i = 0; i < t; ++i) { + libff::alt_bn128_G2 value; + value.Z = libff::alt_bn128_Fq2::one(); + value.X.c0 = libff::alt_bn128_Fq(verificationVectorMult[i][0].asCString()); + value.X.c1 = libff::alt_bn128_Fq(verificationVectorMult[i][1].asCString()); + value.Y.c0 = libff::alt_bn128_Fq(verificationVectorMult[i][2].asCString()); + value.Y.c1 = libff::alt_bn128_Fq(verificationVectorMult[i][3].asCString()); + verificationValue = verificationValue + value; + } + verificationValue.to_affine_coordinates(); + REQUIRE(verificationValue == decrypted_share_G2); + + BLSSigShareSet sigShareSet(t, n); + + string hash = SAMPLE_HASH; + + auto hash_arr = make_shared < array < uint8_t, 32 > > (); + + uint64_t binLen; + + if (!hex2carray(hash.c_str(), &binLen, hash_arr->data(), 32)) { + throw SGXException(TEST_INVALID_HEX, "Invalid hash"); + } + + map > coeffs_pkeys_map; + + for (int i = 0; i < t; i++) { + string blsName = "BLS_KEY" + polyNames[i].substr(4); + REQUIRE(client->createBLSPrivateKey(blsName, ethKeys[i], polyNames[i], secShares[i], t, n)); + + pubBLSKeys[i] = client->getBLSPublicKey(blsName); + + string hash = SAMPLE_HASH; + blsSigShares[i] = client->blsSignMessageHash(blsName, hash, t, n); + REQUIRE(blsSigShares[i].length() > 0); + + shared_ptr sig_share_ptr = make_shared(blsSigShares[i]); + BLSSigShare sig(sig_share_ptr, i + 1, t, n); + sigShareSet.addSigShare(make_shared(sig)); + + vector pubKey_vect; + for (uint8_t j = 0; j < 4; j++) { + pubKey_vect.push_back(pubBLSKeys[i][j].asString()); + } + BLSPublicKeyShare pubKey(make_shared < vector < string >> (pubKey_vect), t, n); + REQUIRE(pubKey.VerifySigWithHelper(hash_arr, make_shared(sig), t, n)); + + coeffs_pkeys_map[i + 1] = make_shared(pubKey); + } + + shared_ptr commonSig = sigShareSet.merge(); + BLSPublicKey + common_public(make_shared < map < size_t, shared_ptr < BLSPublicKeyShare >>>(coeffs_pkeys_map), t, n); + REQUIRE(common_public.VerifySigWithHelper(hash_arr, commonSig, t, n)); +} + TEST_CASE_METHOD(TestFixture, "AES encrypt/decrypt", "[aes-encrypt-decrypt]") { int errStatus = 0; vector errMsg(BUF_LEN, 0); @@ -955,8 +1173,6 @@ TEST_CASE_METHOD(TestFixture, "Exportable / non-exportable keys", "[exportable-n sleep(3); } - - TEST_CASE_METHOD(TestFixture, "Many threads ecdsa dkg v2 bls", "[many-threads-crypto-v2]") { vector threads; int num_threads = 4; @@ -969,7 +1185,17 @@ TEST_CASE_METHOD(TestFixture, "Many threads ecdsa dkg v2 bls", "[many-threads-cr } } +TEST_CASE_METHOD(TestFixture, "Many threads ecdsa dkg v2 bls zmq", "[many-threads-crypto-v2-zmq]") { + vector threads; + int num_threads = 4; + for (int i = 0; i < num_threads; i++) { + threads.push_back(thread(TestUtils::sendRPCRequestZMQ)); + } + for (auto &thread : threads) { + thread.join(); + } +} TEST_CASE_METHOD(TestFixture, "First run", "[first-run]") { @@ -988,8 +1214,6 @@ TEST_CASE_METHOD(TestFixture, "First run", "[first-run]") { } sleep(3); - - } TEST_CASE_METHOD(TestFixtureNoReset, "Second run", "[second-run]") { @@ -1012,15 +1236,69 @@ TEST_CASE_METHOD(TestFixtureNoReset, "Second run", "[second-run]") { } } +TEST_CASE_METHOD(TestFixture, "Test decryption share for threshold encryption", "[te-decryption-share]") { + HttpClient client(RPC_ENDPOINT); + StubClient c(client, JSONRPC_CLIENT_V2); + + std::string key_str = "0xe632f7fde2c90a073ec43eaa90dca7b82476bf28815450a11191484934b9c3f"; + std::string name = "BLS_KEY:SCHAIN_ID:123456789:NODE_ID:0:DKG_ID:0"; + c.importBLSKeyShare(key_str, name); + + // the same key writtn in decimal + libff::alt_bn128_Fr key = libff::alt_bn128_Fr( + "6507625568967977077291849236396320012317305261598035438182864059942098934847"); + + libff::alt_bn128_G2 decryption_value = libff::alt_bn128_G2::random_element(); + decryption_value.to_affine_coordinates(); + + auto decrytion_value_str = convertG2ToString( decryption_value, ':' ); + auto decryption_share = c.getDecryptionShare( name, decrytion_value_str )["decryptionShare"]; + + libff::alt_bn128_G2 share; + share.Z = libff::alt_bn128_Fq2::one(); + + share.X.c0 = libff::alt_bn128_Fq( decryption_share[0].asCString() ); + share.X.c1 = libff::alt_bn128_Fq( decryption_share[1].asCString() ); + share.Y.c0 = libff::alt_bn128_Fq( decryption_share[2].asCString() ); + share.Y.c1 = libff::alt_bn128_Fq( decryption_share[3].asCString() ); + + REQUIRE( share == key * decryption_value ); +} + +TEST_CASE_METHOD(TestFixture, "Test decryption share for threshold encryption via zmq", "[te-decryption-share-zmq]") { + auto client = make_shared(ZMQ_IP, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", + "./sgx_data/cert_data/rootCA.key"); -TEST_CASE_METHOD(TestFixtureZMQSign, "ZMQ-ecdsa", "[zmq-ecdsa]") { + std::string key_str = "0xe632f7fde2c90a073ec43eaa90dca7b82476bf28815450a11191484934b9c3f"; + std::string name = "BLS_KEY:SCHAIN_ID:123456789:NODE_ID:0:DKG_ID:0"; + client->importBLSKeyShare(key_str, name); + // the same key writtn in decimal + libff::alt_bn128_Fr key = libff::alt_bn128_Fr( + "6507625568967977077291849236396320012317305261598035438182864059942098934847"); + + libff::alt_bn128_G2 decryption_value = libff::alt_bn128_G2::random_element(); + decryption_value.to_affine_coordinates(); + + auto decrytion_value_str = convertG2ToString( decryption_value, ':' ); + auto decryption_share = client->getDecryptionShare( name, decrytion_value_str ); + + libff::alt_bn128_G2 share; + share.Z = libff::alt_bn128_Fq2::one(); + + share.X.c0 = libff::alt_bn128_Fq( decryption_share[0].asCString() ); + share.X.c1 = libff::alt_bn128_Fq( decryption_share[1].asCString() ); + share.Y.c0 = libff::alt_bn128_Fq( decryption_share[2].asCString() ); + share.Y.c1 = libff::alt_bn128_Fq( decryption_share[3].asCString() ); + + REQUIRE( share == key * decryption_value ); +} + +TEST_CASE_METHOD(TestFixtureZMQSign, "ZMQ-ecdsa", "[zmq-ecdsa]") { HttpClient htp(RPC_ENDPOINT); StubClient c(htp, JSONRPC_CLIENT_V2); - string ip = ZMQ_IP; - - auto client = make_shared(ip, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", + auto client = make_shared(ZMQ_IP, ZMQ_PORT, true, "./sgx_data/cert_data/rootCA.pem", "./sgx_data/cert_data/rootCA.key"); string keyName = ""; @@ -1030,10 +1308,8 @@ TEST_CASE_METHOD(TestFixtureZMQSign, "ZMQ-ecdsa", "[zmq-ecdsa]") { int end = 10000000; string sh = string(SAMPLE_HASH); - std::vector workers; - PRINT_SRC_LINE for (int j = 0; j < 2; j++) { @@ -1056,6 +1332,4 @@ TEST_CASE_METHOD(TestFixtureZMQSign, "ZMQ-ecdsa", "[zmq-ecdsa]") { } - -TEST_CASE_METHOD(TestFixtureNoResetFromBackup, "Backup restore", "[backup-restore]") { -} +TEST_CASE_METHOD(TestFixtureNoResetFromBackup, "Backup restore", "[backup-restore]") {} diff --git a/testw.py b/testw.py index 12d5f093..ed4e8c62 100755 --- a/testw.py +++ b/testw.py @@ -29,36 +29,42 @@ topDir = os.getcwd() + "/sgxwallet" print("Top directory is:" + topDir) testList = [ "[zmq-ecdsa]", - "[dkgzmqbls]", "[first-run]", - "[second-run]", - "[many-threads-crypto]", - "[many-threads-crypto-v2]", - "[backup-restore]", - "[cert-sign]", - "[get-server-status]", - "[get-server-version]", - "[backup-key]", - "[delete-bls-key]", - "[import-ecdsa-key]", - "[ecdsa-aes-key-gen]", - "[ecdsa-aes-key-sig-gen]", - "[ecdsa-aes-get-pub-key]", - "[ecdsa-key-gen-api]", - "[bls-key-encrypt]", - "[dkg-aes-gen]", - "[dkg-aes-encr-sshares]", - "[dkg-aes-encr-sshares-v2]", - "[dkg-api]", - "[dkg-api-v2]", - "[dkg-bls]", - "[dkg-bls-v2]", - "[dkg-poly-exists]", - "[dkg-aes-pub-shares]", - "[aes-encrypt-decrypt]", - "[exportable-nonexportable-keys]", - "[aes-dkg]", - "[aes-dkg-v2]" + "[second-run]", + "[many-threads-crypto-v2]", + "[many-threads-crypto-v2-zmq]" + "[backup-restore]", + "[cert-sign]", + "[get-server-status]", + "[get-server-status-zmq]", + "[get-server-version]", + "[get-server-version-zmq]", + "[backup-key]", + "[delete-bls-key]", + "[delete-bls-key-zmq]", + "[import-ecdsa-key]", + "[import-ecdsa-key-zmq]", + "[ecdsa-aes-key-gen]", + "[ecdsa-aes-key-sig-gen]", + "[ecdsa-aes-get-pub-key]", + "[ecdsa-key-gen-api]", + "[bls-key-encrypt]", + "[dkg-aes-gen]", + "[dkg-aes-encr-sshares]", + "[dkg-aes-encr-sshares-v2]", + "[dkg-api-v2]", + "[dkg-api-v2-zmq]", + "[dkg-bls]", + "[dkg-bls-v2]", + "[dkg-poly-exists]", + "[dkg-poly-exists-zmq]", + "[dkg-aes-pub-shares]", + "[aes-encrypt-decrypt]", + "[exportable-nonexportable-keys]", + "[aes-dkg-v2]", + "[aes-dkg-v2-zmq]", + "[te-decryption-share]", + "[te-decryption-share-zmq]" ] diff --git a/third_party/atomicops.h b/third_party/atomicops.h new file mode 100644 index 00000000..74221734 --- /dev/null +++ b/third_party/atomicops.h @@ -0,0 +1,784 @@ +// ©2013-2016 Cameron Desrochers. +// Distributed under the simplified BSD license (see the license file that +// should have come with this header). +// Uses Jeff Preshing's semaphore implementation (under the terms of its +// separate zlib license, embedded below). + +#pragma once + +// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant) implementation +// of low-level memory barriers, plus a few semi-portable utility macros (for inlining and alignment). +// Also has a basic atomic type (limited to hardware-supported atomics with no memory ordering guarantees). +// Uses the AE_* prefix for macros (historical reasons), and the "moodycamel" namespace for symbols. + +#include +#include +#include +#include +#include +#include + +// Platform detection +#if defined(__INTEL_COMPILER) +#define AE_ICC +#elif defined(_MSC_VER) +#define AE_VCPP +#elif defined(__GNUC__) +#define AE_GCC +#endif + +#if defined(_M_IA64) || defined(__ia64__) +#define AE_ARCH_IA64 +#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__) +#define AE_ARCH_X64 +#elif defined(_M_IX86) || defined(__i386__) +#define AE_ARCH_X86 +#elif defined(_M_PPC) || defined(__powerpc__) +#define AE_ARCH_PPC +#else +#define AE_ARCH_UNKNOWN +#endif + + +// AE_UNUSED +#define AE_UNUSED(x) ((void)x) + +// AE_NO_TSAN/AE_TSAN_ANNOTATE_* +#if defined(__has_feature) +#if __has_feature(thread_sanitizer) +#if __cplusplus >= 201703L // inline variables require C++17 +namespace moodycamel { inline int ae_tsan_global; } +#define AE_TSAN_ANNOTATE_RELEASE() AnnotateHappensBefore(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global)) +#define AE_TSAN_ANNOTATE_ACQUIRE() AnnotateHappensAfter(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global)) +extern "C" void AnnotateHappensBefore(const char*, int, void*); +extern "C" void AnnotateHappensAfter(const char*, int, void*); +#else // when we can't work with tsan, attempt to disable its warnings +#define AE_NO_TSAN __attribute__((no_sanitize("thread"))) +#endif +#endif +#endif +#ifndef AE_NO_TSAN +#define AE_NO_TSAN +#endif +#ifndef AE_TSAN_ANNOTATE_RELEASE +#define AE_TSAN_ANNOTATE_RELEASE() +#define AE_TSAN_ANNOTATE_ACQUIRE() +#endif + + +// AE_FORCEINLINE +#if defined(AE_VCPP) || defined(AE_ICC) +#define AE_FORCEINLINE __forceinline +#elif defined(AE_GCC) +//#define AE_FORCEINLINE __attribute__((always_inline)) +#define AE_FORCEINLINE inline +#else +#define AE_FORCEINLINE inline +#endif + + +// AE_ALIGN +#if defined(AE_VCPP) || defined(AE_ICC) +#define AE_ALIGN(x) __declspec(align(x)) +#elif defined(AE_GCC) +#define AE_ALIGN(x) __attribute__((aligned(x))) +#else +// Assume GCC compliant syntax... +#define AE_ALIGN(x) __attribute__((aligned(x))) +#endif + + +// Portable atomic fences implemented below: + +namespace moodycamel { + + enum memory_order { + memory_order_relaxed, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, + + // memory_order_sync: Forces a full sync: + // #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad + memory_order_sync = memory_order_seq_cst + }; + +} // end namespace moodycamel + +#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || (defined(AE_ICC) && __INTEL_COMPILER < 1600) +// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences + +#include + +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) +#define AeFullSync _mm_mfence +#define AeLiteSync _mm_mfence +#elif defined(AE_ARCH_IA64) +#define AeFullSync __mf +#define AeLiteSync __mf +#elif defined(AE_ARCH_PPC) +#include +#define AeFullSync __sync +#define AeLiteSync __lwsync +#endif + + +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable: 4365) // Disable erroneous 'conversion from long to unsigned int, signed/unsigned mismatch' error when using `assert` +#ifdef __cplusplus_cli +#pragma managed(push, off) +#endif +#endif + +namespace moodycamel { + +AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN +{ + switch (order) { + case memory_order_relaxed: break; + case memory_order_acquire: _ReadBarrier(); break; + case memory_order_release: _WriteBarrier(); break; + case memory_order_acq_rel: _ReadWriteBarrier(); break; + case memory_order_seq_cst: _ReadWriteBarrier(); break; + default: assert(false); + } +} + +// x86/x64 have a strong memory model -- all loads and stores have +// acquire and release semantics automatically (so only need compiler +// barriers for those). +#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64) +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN +{ + switch (order) { + case memory_order_relaxed: break; + case memory_order_acquire: _ReadBarrier(); break; + case memory_order_release: _WriteBarrier(); break; + case memory_order_acq_rel: _ReadWriteBarrier(); break; + case memory_order_seq_cst: + _ReadWriteBarrier(); + AeFullSync(); + _ReadWriteBarrier(); + break; + default: assert(false); + } +} +#else +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN +{ + // Non-specialized arch, use heavier memory barriers everywhere just in case :-( + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + _ReadBarrier(); + AeLiteSync(); + _ReadBarrier(); + break; + case memory_order_release: + _WriteBarrier(); + AeLiteSync(); + _WriteBarrier(); + break; + case memory_order_acq_rel: + _ReadWriteBarrier(); + AeLiteSync(); + _ReadWriteBarrier(); + break; + case memory_order_seq_cst: + _ReadWriteBarrier(); + AeFullSync(); + _ReadWriteBarrier(); + break; + default: assert(false); + } +} +#endif +} // end namespace moodycamel +#else +// Use standard library of atomics +#include + +namespace moodycamel { + + AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN + { + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + std::atomic_signal_fence(std::memory_order_acquire); + break; + case memory_order_release: + std::atomic_signal_fence(std::memory_order_release); + break; + case memory_order_acq_rel: + std::atomic_signal_fence(std::memory_order_acq_rel); + break; + case memory_order_seq_cst: + std::atomic_signal_fence(std::memory_order_seq_cst); + break; + default: + assert(false); + } + } + + AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN + { + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + AE_TSAN_ANNOTATE_ACQUIRE(); + std::atomic_thread_fence(std::memory_order_acquire); + break; + case memory_order_release: + AE_TSAN_ANNOTATE_RELEASE(); + std::atomic_thread_fence(std::memory_order_release); + break; + case memory_order_acq_rel: + AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); + std::atomic_thread_fence(std::memory_order_acq_rel); + break; + case memory_order_seq_cst: + AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); + std::atomic_thread_fence(std::memory_order_seq_cst); + break; + default: + assert(false); + } + } + +} // end namespace moodycamel + +#endif + + +#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli)) +#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC +#endif + +#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC + +#include + +#endif + +#include + +// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY: +// Provides basic support for atomic variables -- no memory ordering guarantees are provided. +// The guarantee of atomicity is only made for types that already have atomic load and store guarantees +// at the hardware level -- on most platforms this generally means aligned pointers and integers (only). +namespace moodycamel { + template + class weak_atomic { + public: + AE_NO_TSAN weak_atomic() : value() {} + +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable: 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning +#endif + + template + AE_NO_TSAN weak_atomic(U &&x) : value(std::forward(x)) {} + +#ifdef __cplusplus_cli + // Work around bug with universal reference/nullptr combination that only appears when /clr is on + AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) { } +#endif + AE_NO_TSAN weak_atomic(weak_atomic const &other) : value(other.load()) {} + + AE_NO_TSAN weak_atomic(weak_atomic &&other) : value(std::move(other.load())) {} + +#ifdef AE_VCPP +#pragma warning(pop) +#endif + + AE_FORCEINLINE operator T() const AE_NO_TSAN { return load(); } + + +#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC + template AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { value = std::forward(x); return *this; } + AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { value = other.value; return *this; } + + AE_FORCEINLINE T load() const AE_NO_TSAN { return value; } + + AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN + { +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) + if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); +#if defined(_M_AMD64) + else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); +#endif +#else +#error Unsupported platform +#endif + assert(false && "T must be either a 32 or 64 bit type"); + return value; + } + + AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN + { +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) + if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); +#if defined(_M_AMD64) + else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); +#endif +#else +#error Unsupported platform +#endif + assert(false && "T must be either a 32 or 64 bit type"); + return value; + } +#else + + template + AE_FORCEINLINE weak_atomic const &operator=(U &&x) AE_NO_TSAN { + value.store(std::forward(x), std::memory_order_relaxed); + return *this; + } + + AE_FORCEINLINE weak_atomic const &operator=(weak_atomic const &other) AE_NO_TSAN + { + value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed); + return *this; + } + + AE_FORCEINLINE T load() const AE_NO_TSAN { return value.load(std::memory_order_relaxed); } + + AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN + { + return value.fetch_add(increment, std::memory_order_acquire); + } + + AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN + { + return value.fetch_add(increment, std::memory_order_release); + } + +#endif + + + private: +#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC + // No std::atomic support, but still need to circumvent compiler optimizations. + // `volatile` will make memory access slow, but is guaranteed to be reliable. + volatile T value; +#else + std::atomic value; +#endif + }; + +} // end namespace moodycamel + + + +// Portable single-producer, single-consumer semaphore below: + +#if defined(_WIN32) +// Avoid including windows.h in a header; we only need a handful of +// items, so we'll redeclare them here (this is relatively safe since +// the API generally has to remain stable between Windows versions). +// I know this is an ugly hack but it still beats polluting the global +// namespace with thousands of generic names or adding a .cpp for nothing. +extern "C" { + struct _SECURITY_ATTRIBUTES; + __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName); + __declspec(dllimport) int __stdcall CloseHandle(void* hObject); + __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds); + __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount); +} +#elif defined(__MACH__) +#include +#elif defined(__unix__) +#include +#elif defined(FREERTOS) +#include +#include +#include +#endif + +namespace moodycamel { + // Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's + // portable + lightweight semaphore implementations, originally from + // https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h + // LICENSE: + // Copyright (c) 2015 Jeff Preshing + // + // This software is provided 'as-is', without any express or implied + // warranty. In no event will the authors be held liable for any damages + // arising from the use of this software. + // + // Permission is granted to anyone to use this software for any purpose, + // including commercial applications, and to alter it and redistribute it + // freely, subject to the following restrictions: + // + // 1. The origin of this software must not be misrepresented; you must not + // claim that you wrote the original software. If you use this software + // in a product, an acknowledgement in the product documentation would be + // appreciated but is not required. + // 2. Altered source versions must be plainly marked as such, and must not be + // misrepresented as being the original software. + // 3. This notice may not be removed or altered from any source distribution. + namespace spsc_sema { +#if defined(_WIN32) + class Semaphore + { + private: + void* m_hSema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + + public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema() + { + assert(initialCount >= 0); + const long maxLong = 0x7fffffff; + m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); + assert(m_hSema); + } + + AE_NO_TSAN ~Semaphore() + { + CloseHandle(m_hSema); + } + + bool wait() AE_NO_TSAN + { + const unsigned long infinite = 0xffffffff; + return WaitForSingleObject(m_hSema, infinite) == 0; + } + + bool try_wait() AE_NO_TSAN + { + return WaitForSingleObject(m_hSema, 0) == 0; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN + { + return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; + } + + void signal(int count = 1) AE_NO_TSAN + { + while (!ReleaseSemaphore(m_hSema, count, nullptr)); + } + }; +#elif defined(__MACH__) + //--------------------------------------------------------- + // Semaphore (Apple iOS and OSX) + // Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html + //--------------------------------------------------------- + class Semaphore + { + private: + semaphore_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + + public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() + { + assert(initialCount >= 0); + kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); + assert(rc == KERN_SUCCESS); + AE_UNUSED(rc); + } + + AE_NO_TSAN ~Semaphore() + { + semaphore_destroy(mach_task_self(), m_sema); + } + + bool wait() AE_NO_TSAN + { + return semaphore_wait(m_sema) == KERN_SUCCESS; + } + + bool try_wait() AE_NO_TSAN + { + return timed_wait(0); + } + + bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN + { + mach_timespec_t ts; + ts.tv_sec = static_cast(timeout_usecs / 1000000); + ts.tv_nsec = static_cast((timeout_usecs % 1000000) * 1000); + + // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html + kern_return_t rc = semaphore_timedwait(m_sema, ts); + return rc == KERN_SUCCESS; + } + + void signal() AE_NO_TSAN + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + + void signal(int count) AE_NO_TSAN + { + while (count-- > 0) + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + } + }; +#elif defined(__unix__) + //--------------------------------------------------------- + // Semaphore (POSIX, Linux) + //--------------------------------------------------------- + class Semaphore + { + private: + sem_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + + public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() + { + assert(initialCount >= 0); + int rc = sem_init(&m_sema, 0, static_cast(initialCount)); + assert(rc == 0); + AE_UNUSED(rc); + } + + AE_NO_TSAN ~Semaphore() + { + sem_destroy(&m_sema); + } + + bool wait() AE_NO_TSAN + { + // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error + int rc; + do + { + rc = sem_wait(&m_sema); + } + while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool try_wait() AE_NO_TSAN + { + int rc; + do { + rc = sem_trywait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN + { + struct timespec ts; + const int usecs_in_1_sec = 1000000; + const int nsecs_in_1_sec = 1000000000; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += static_cast(usecs / usecs_in_1_sec); + ts.tv_nsec += static_cast(usecs % usecs_in_1_sec) * 1000; + // sem_timedwait bombs if you have more than 1e9 in tv_nsec + // so we have to clean things up before passing it in + if (ts.tv_nsec >= nsecs_in_1_sec) { + ts.tv_nsec -= nsecs_in_1_sec; + ++ts.tv_sec; + } + + int rc; + do { + rc = sem_timedwait(&m_sema, &ts); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + void signal() AE_NO_TSAN + { + while (sem_post(&m_sema) == -1); + } + + void signal(int count) AE_NO_TSAN + { + while (count-- > 0) + { + while (sem_post(&m_sema) == -1); + } + } + }; +#elif defined(FREERTOS) + //--------------------------------------------------------- + // Semaphore (FreeRTOS) + //--------------------------------------------------------- + class Semaphore + { + private: + SemaphoreHandle_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + + public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() + { + assert(initialCount >= 0); + m_sema = xSemaphoreCreateCounting(static_cast(~0ull), static_cast(initialCount)); + assert(m_sema); + } + + AE_NO_TSAN ~Semaphore() + { + vSemaphoreDelete(m_sema); + } + + bool wait() AE_NO_TSAN + { + return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE; + } + + bool try_wait() AE_NO_TSAN + { + // Note: In an ISR context, if this causes a task to unblock, + // the caller won't know about it + if (xPortIsInsideInterrupt()) + return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE; + return xSemaphoreTake(m_sema, 0) == pdTRUE; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN + { + std::uint64_t msecs = usecs / 1000; + TickType_t ticks = static_cast(msecs / portTICK_PERIOD_MS); + if (ticks == 0) + return try_wait(); + return xSemaphoreTake(m_sema, ticks) == pdTRUE; + } + + void signal() AE_NO_TSAN + { + // Note: In an ISR context, if this causes a task to unblock, + // the caller won't know about it + BaseType_t rc; + if (xPortIsInsideInterrupt()) + rc = xSemaphoreGiveFromISR(m_sema, NULL); + else + rc = xSemaphoreGive(m_sema); + assert(rc == pdTRUE); + AE_UNUSED(rc); + } + + void signal(int count) AE_NO_TSAN + { + while (count-- > 0) + signal(); + } + }; +#else +#error Unsupported platform! (No semaphore wrapper available) +#endif + + //--------------------------------------------------------- + // LightweightSemaphore + //--------------------------------------------------------- + class LightweightSemaphore { + public: + typedef std::make_signed::type ssize_t; + + private: + weak_atomic m_count; + Semaphore m_sema; + + bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN + { + ssize_t oldCount; + // Is there a better way to set the initial spin count? + // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC, + // as threads start hitting the kernel semaphore. + int spin = 1024; + while (--spin >= 0) { + if (m_count.load() > 0) { + m_count.fetch_add_acquire(-1); + return true; + } + compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop. + } + oldCount = m_count.fetch_add_acquire(-1); + if (oldCount > 0) + return true; + if (timeout_usecs < 0) { + if (m_sema.wait()) + return true; + } + if (timeout_usecs > 0 && m_sema.timed_wait(static_cast(timeout_usecs))) + return true; + // At this point, we've timed out waiting for the semaphore, but the + // count is still decremented indicating we may still be waiting on + // it. So we have to re-adjust the count, but only if the semaphore + // wasn't signaled enough times for us too since then. If it was, we + // need to release the semaphore too. + while (true) { + oldCount = m_count.fetch_add_release(1); + if (oldCount < 0) + return false; // successfully restored things to the way they were + // Oh, the producer thread just signaled the semaphore after all. Try again: + oldCount = m_count.fetch_add_acquire(-1); + if (oldCount > 0 && m_sema.try_wait()) + return true; + } + } + + public: + AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema() { + assert(initialCount >= 0); + } + + bool tryWait() AE_NO_TSAN + { + if (m_count.load() > 0) { + m_count.fetch_add_acquire(-1); + return true; + } + return false; + } + + bool wait() AE_NO_TSAN + { + return tryWait() || waitWithPartialSpinning(); + } + + bool wait(std::int64_t timeout_usecs) AE_NO_TSAN + { + return tryWait() || waitWithPartialSpinning(timeout_usecs); + } + + void signal(ssize_t count = 1) AE_NO_TSAN + { + assert(count >= 0); + ssize_t oldCount = m_count.fetch_add_release(count); + assert(oldCount >= -1); + if (oldCount < 0) { + m_sema.signal(1); + } + } + + std::size_t availableApprox() const AE_NO_TSAN + { + ssize_t count = m_count.load(); + return count > 0 ? static_cast(count) : 0; + } + }; + } // end namespace spsc_sema +} // end namespace moodycamel + +#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli)) +#pragma warning(pop) +#ifdef __cplusplus_cli +#pragma managed(pop) +#endif +#endif diff --git a/third_party/concurrentqueue.h b/third_party/concurrentqueue.h new file mode 100644 index 00000000..13724a51 --- /dev/null +++ b/third_party/concurrentqueue.h @@ -0,0 +1,3955 @@ +// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. +// An overview, including benchmark results, is provided here: +// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ +// The full design is also described in excruciating detail at: +// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue + +// Simplified BSD license: +// Copyright (c) 2013-2020, Cameron Desrochers. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Also dual-licensed under the Boost Software License (see LICENSE.md) + +#pragma once + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and +// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings +// upon assigning any computed values) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" + +#ifdef MCDBGQ_USE_RELACY +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +#endif +#endif + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +// VS2019 with /W4 warns about constant conditional expressions but unless /std=c++17 or higher +// does not support `if constexpr`, so we have no choice but to simply disable the warning +#pragma warning(push) +#pragma warning(disable: 4127) // conditional expression is constant +#endif + +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif + +#ifdef MCDBGQ_USE_RELACY +#include "relacy/relacy_std.hpp" +#include "relacy_shims.h" +// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. +// We'll override the default trait malloc ourselves without a macro. +#undef new +#undef delete +#undef malloc +#undef free +#else + +#include // Requires C++11. Sorry VS2010. +#include + +#endif + +#include // for max_align_t +#include +#include +#include +#include +#include +#include +#include // for CHAR_BIT +#include +#include // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading + +// Platform-specific definitions of a numeric thread ID type and an invalid value +namespace moodycamel { + namespace details { + template + struct thread_id_converter { + typedef thread_id_t thread_id_numeric_size_t; + typedef thread_id_t thread_id_hash_t; + + static thread_id_hash_t prehash(thread_id_t const &x) { return x; } + }; + } +} +#if defined(MCDBGQ_USE_RELACY) +namespace moodycamel { namespace details { + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; + static inline thread_id_t thread_id() { return rl::thread_index(); } +} } +#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) +// No sense pulling in windows.h in a header, we'll manually declare the function +// we use and rely on backwards-compatibility for this not to break +extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +namespace moodycamel { namespace details { + static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. + static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } +} } +#elif defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) || (defined(__APPLE__) && TARGET_OS_IPHONE) || defined(MOODYCAMEL_NO_THREAD_LOCAL) +namespace moodycamel { namespace details { + static_assert(sizeof(std::thread::id) == 4 || sizeof(std::thread::id) == 8, "std::thread::id is expected to be either 4 or 8 bytes"); + + typedef std::thread::id thread_id_t; + static const thread_id_t invalid_thread_id; // Default ctor creates invalid ID + + // Note we don't define a invalid_thread_id2 since std::thread::id doesn't have one; it's + // only used if MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is defined anyway, which it won't + // be. + static inline thread_id_t thread_id() { return std::this_thread::get_id(); } + + template struct thread_id_size { }; + template<> struct thread_id_size<4> { typedef std::uint32_t numeric_t; }; + template<> struct thread_id_size<8> { typedef std::uint64_t numeric_t; }; + + template<> struct thread_id_converter { + typedef thread_id_size::numeric_t thread_id_numeric_size_t; +#ifndef __APPLE__ + typedef std::size_t thread_id_hash_t; +#else + typedef thread_id_numeric_size_t thread_id_hash_t; +#endif + + static thread_id_hash_t prehash(thread_id_t const& x) + { +#ifndef __APPLE__ + return std::hash()(x); +#else + return *reinterpret_cast(&x); +#endif + } + }; +} } +#else +// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 +// In order to get a numeric thread ID in a platform-independent way, we use a thread-local +// static variable's address as a thread identifier :-) +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +#define MOODYCAMEL_THREADLOCAL __thread +#elif defined(_MSC_VER) +#define MOODYCAMEL_THREADLOCAL __declspec(thread) +#else +// Assume C++11 compliant compiler +#define MOODYCAMEL_THREADLOCAL thread_local +#endif +namespace moodycamel { + namespace details { + typedef std::uintptr_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr + static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. + inline thread_id_t thread_id() { + static MOODYCAMEL_THREADLOCAL int x; + return reinterpret_cast(&x); + } + } +} +#endif + +// Constexpr if +#ifndef MOODYCAMEL_CONSTEXPR_IF +#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || __cplusplus > 201402L +#define MOODYCAMEL_CONSTEXPR_IF if constexpr +#define MOODYCAMEL_MAYBE_UNUSED [[maybe_unused]] +#else +#define MOODYCAMEL_CONSTEXPR_IF if +#define MOODYCAMEL_MAYBE_UNUSED +#endif +#endif + +// Exceptions +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#endif +#endif +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED +#define MOODYCAMEL_TRY try +#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) +#define MOODYCAMEL_RETHROW throw +#define MOODYCAMEL_THROW(expr) throw (expr) +#else +#define MOODYCAMEL_TRY MOODYCAMEL_CONSTEXPR_IF (true) +#define MOODYCAMEL_CATCH(...) else MOODYCAMEL_CONSTEXPR_IF (false) +#define MOODYCAMEL_RETHROW +#define MOODYCAMEL_THROW(expr) +#endif + +#ifndef MOODYCAMEL_NOEXCEPT +#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) +#define MOODYCAMEL_NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 +// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( +// We have to assume *all* non-trivial constructors may throw on VS2012! +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#else +#define MOODYCAMEL_NOEXCEPT noexcept +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) +#endif +#endif + +#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#else +// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 +// g++ <=4.7 doesn't support thread_local either. +// Finally, iOS/ARM doesn't have support for it either, and g++/ARM allows it to compile but it's unconfirmed to actually work +#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && (!defined(__APPLE__) || !TARGET_OS_IPHONE) && !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__) +// Assume `thread_local` is fully supported in all other C++11 compilers/platforms +//#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED // always disabled for now since several users report having problems with it on +#endif +#endif +#endif + +// VS2012 doesn't support deleted functions. +// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. +#ifndef MOODYCAMEL_DELETE_FUNCTION +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define MOODYCAMEL_DELETE_FUNCTION +#else +#define MOODYCAMEL_DELETE_FUNCTION = delete +#endif +#endif + +namespace moodycamel { + namespace details { +#ifndef MOODYCAMEL_ALIGNAS +// VS2013 doesn't support alignas or alignof, and align() requires a constant literal +#if defined(_MSC_VER) && _MSC_VER <= 1800 +#define MOODYCAMEL_ALIGNAS(alignment) __declspec(align(alignment)) +#define MOODYCAMEL_ALIGNOF(obj) __alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) typename details::Vs2013Aligned::value, T>::type + template struct Vs2013Aligned { }; // default, unsupported alignment + template struct Vs2013Aligned<1, T> { typedef __declspec(align(1)) T type; }; + template struct Vs2013Aligned<2, T> { typedef __declspec(align(2)) T type; }; + template struct Vs2013Aligned<4, T> { typedef __declspec(align(4)) T type; }; + template struct Vs2013Aligned<8, T> { typedef __declspec(align(8)) T type; }; + template struct Vs2013Aligned<16, T> { typedef __declspec(align(16)) T type; }; + template struct Vs2013Aligned<32, T> { typedef __declspec(align(32)) T type; }; + template struct Vs2013Aligned<64, T> { typedef __declspec(align(64)) T type; }; + template struct Vs2013Aligned<128, T> { typedef __declspec(align(128)) T type; }; + template struct Vs2013Aligned<256, T> { typedef __declspec(align(256)) T type; }; +#else + template + struct identity { + typedef T type; + }; +#define MOODYCAMEL_ALIGNAS(alignment) alignas(alignment) +#define MOODYCAMEL_ALIGNOF(obj) alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) alignas(alignof(obj)) typename details::identity::type +#endif +#endif + } +} + + +// TSAN can false report races in lock-free code. To enable TSAN to be used from projects that use this one, +// we can apply per-function compile-time suppression. +// See https://clang.llvm.org/docs/ThreadSanitizer.html#has-feature-thread-sanitizer +#define MOODYCAMEL_NO_TSAN +#if defined(__has_feature) +#if __has_feature(thread_sanitizer) +#undef MOODYCAMEL_NO_TSAN +#define MOODYCAMEL_NO_TSAN __attribute__((no_sanitize("thread"))) +#endif // TSAN +#endif // TSAN + +// Compiler-specific likely/unlikely hints +namespace moodycamel { + namespace details { +#if defined(__GNUC__) + static inline bool (likely)(bool x) { return __builtin_expect((x), true); } + static inline bool (unlikely)(bool x) { return __builtin_expect((x), false); } +#else + + static inline bool (likely)(bool x) { return x; } + + static inline bool (unlikely)(bool x) { return x; } + +#endif + } +} + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG +#include "internal/concurrentqueue_internal_debug.h" +#endif + +namespace moodycamel { + namespace details { + template + struct const_numeric_max { + static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); + static const T value = std::numeric_limits::is_signed + ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) + : static_cast(-1); + }; + +#if defined(__GLIBCXX__) + typedef ::max_align_t std_max_align_t; // libstdc++ forgot to add it to std:: for a while +#else + typedef std::max_align_t std_max_align_t; // Others (e.g. MSVC) insist it can *only* be accessed via std:: +#endif + + // Some platforms have incorrectly set max_align_t to a type with <8 bytes alignment even while supporting + // 8-byte aligned scalar values (*cough* 32-bit iOS). Work around this with our own union. See issue #64. + typedef union { + std_max_align_t x; + long long y; + void *z; + } max_align_t; + } + +// Default traits for the ConcurrentQueue. To change some of the +// traits without re-implementing all of them, inherit from this +// struct and shadow the declarations you wish to be different; +// since the traits are used as a template type parameter, the +// shadowed declarations will be used where defined, and the defaults +// otherwise. + struct ConcurrentQueueDefaultTraits { + // General-purpose size type. std::size_t is strongly recommended. + typedef std::size_t size_t; + + // The type used for the enqueue and dequeue indices. Must be at least as + // large as size_t. Should be significantly larger than the number of elements + // you expect to hold at once, especially if you have a high turnover rate; + // for example, on 32-bit x86, if you expect to have over a hundred million + // elements or pump several million elements through your queue in a very + // short space of time, using a 32-bit type *may* trigger a race condition. + // A 64-bit int type is recommended in that case, and in practice will + // prevent a race condition no matter the usage of the queue. Note that + // whether the queue is lock-free with a 64-int type depends on the whether + // std::atomic is lock-free, which is platform-specific. + typedef std::size_t index_t; + + // Internally, all elements are enqueued and dequeued from multi-element + // blocks; this is the smallest controllable unit. If you expect few elements + // but many producers, a smaller block size should be favoured. For few producers + // and/or many elements, a larger block size is preferred. A sane default + // is provided. Must be a power of 2. + static const size_t BLOCK_SIZE = 32; + + // For explicit producers (i.e. when using a producer token), the block is + // checked for being empty by iterating through a list of flags, one per element. + // For large block sizes, this is too inefficient, and switching to an atomic + // counter-based approach is faster. The switch is made for block sizes strictly + // larger than this threshold. + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; + + // How many full blocks can be expected for a single explicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; + + // How many full blocks can be expected for a single implicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; + + // The initial size of the hash table mapping thread IDs to implicit producers. + // Note that the hash is resized every time it becomes half full. + // Must be a power of two, and either 0 or at least 1. If 0, implicit production + // (using the enqueue methods without an explicit producer token) is disabled. + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; + + // Controls the number of items that an explicit consumer (i.e. one with a token) + // must consume before it causes all consumers to rotate and move on to the next + // internal queue. + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; + + // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. + // Enqueue operations that would cause this limit to be surpassed will fail. Note + // that this limit is enforced at the block level (for performance reasons), i.e. + // it's rounded up to the nearest block size. + static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; + + // The number of times to spin before sleeping when waiting on a semaphore. + // Recommended values are on the order of 1000-10000 unless the number of + // consumer threads exceeds the number of idle cores (in which case try 0-100). + // Only affects instances of the BlockingConcurrentQueue. + static const int MAX_SEMA_SPINS = 10000; + + +#ifndef MCDBGQ_USE_RELACY + // Memory allocation can be customized if needed. + // malloc should return nullptr on failure, and handle alignment like std::malloc. +#if defined(malloc) || defined(free) + // Gah, this is 2015, stop defining macros that break standard code already! + // Work around malloc/free being special macros: + static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); } + static inline void WORKAROUND_free(void* ptr) { return free(ptr); } + static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); } + static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); } +#else + + static inline void *malloc(size_t size) { return std::malloc(size); } + + static inline void free(void *ptr) { return std::free(ptr); } + +#endif +#else + // Debug versions when running under the Relacy race detector (ignore + // these in user code) + static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } + static inline void free(void* ptr) { return rl::rl_free(ptr, $); } +#endif + }; + + +// When producing or consuming many elements, the most efficient way is to: +// 1) Use one of the bulk-operation methods of the queue with a token +// 2) Failing that, use the bulk-operation methods without a token +// 3) Failing that, create a token and use that with the single-item methods +// 4) Failing that, use the single-parameter methods of the queue +// Having said that, don't create tokens willy-nilly -- ideally there should be +// a maximum of one token per thread (of each kind). + struct ProducerToken; + struct ConsumerToken; + + template + class ConcurrentQueue; + + template + class BlockingConcurrentQueue; + + class ConcurrentQueueTests; + + + namespace details { + struct ConcurrentQueueProducerTypelessBase { + ConcurrentQueueProducerTypelessBase *next; + std::atomic inactive; + ProducerToken *token; + + ConcurrentQueueProducerTypelessBase() + : next(nullptr), inactive(false), token(nullptr) { + } + }; + + template + struct _hash_32_or_64 { + static inline std::uint32_t hash(std::uint32_t h) { + // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + // Since the thread ID is already unique, all we really want to do is propagate that + // uniqueness evenly across all the bits, so that we can use a subset of the bits while + // reducing collisions significantly + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + return h ^ (h >> 16); + } + }; + + template<> + struct _hash_32_or_64<1> { + static inline std::uint64_t hash(std::uint64_t h) { + h ^= h >> 33; + h *= 0xff51afd7ed558ccd; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53; + return h ^ (h >> 33); + } + }; + + template + struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { + }; + + static inline size_t hash_thread_id(thread_id_t id) { + static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); + return static_cast(hash_32_or_64::thread_id_hash_t)>::hash( + thread_id_converter::prehash(id))); + } + + template + static inline bool circular_less_than(T a, T b) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4554) +#endif + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, + "circular_less_than is intended to be used only with unsigned integer types"); + return static_cast(a - b) > + static_cast(static_cast(1) << static_cast(sizeof(T) * CHAR_BIT - 1)); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } + + template + static inline char *align_for(char *ptr) { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + + template + static inline T ceil_to_pow_2(T x) { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, + "ceil_to_pow_2 is intended to be used only with unsigned integer types"); + + // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (std::size_t i = 1; i < sizeof(T); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static inline void swap_relaxed(std::atomic &left, std::atomic &right) { + T temp = std::move(left.load(std::memory_order_relaxed)); + left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); + right.store(std::move(temp), std::memory_order_relaxed); + } + + template + static inline T const &nomove(T const &x) { + return x; + } + + template + struct nomove_if { + template + static inline T const &eval(T const &x) { + return x; + } + }; + + template<> + struct nomove_if { + template + static inline auto eval(U &&x) + -> + + decltype (std::forward(x)) { + return std::forward(x); + } + }; + + template + static inline auto deref_noexcept(It &it) + + MOODYCAMEL_NOEXCEPT -> + decltype(*it) + { + return *it; + } + +#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + template + struct is_trivially_destructible : std::is_trivially_destructible { + }; +#else + template struct is_trivially_destructible : std::has_trivial_destructor { }; +#endif + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY + typedef RelacyThreadExitListener ThreadExitListener; + typedef RelacyThreadExitNotifier ThreadExitNotifier; +#else + struct ThreadExitListener + { + typedef void (*callback_t)(void*); + callback_t callback; + void* userData; + + ThreadExitListener* next; // reserved for use by the ThreadExitNotifier + }; + + + class ThreadExitNotifier + { + public: + static void subscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + listener->next = tlsInst.tail; + tlsInst.tail = listener; + } + + static void unsubscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + ThreadExitListener** prev = &tlsInst.tail; + for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { + if (ptr == listener) { + *prev = ptr->next; + break; + } + prev = &ptr->next; + } + } + + private: + ThreadExitNotifier() : tail(nullptr) { } + ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + + ~ThreadExitNotifier() + { + // This thread is about to exit, let everyone know! + assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); + for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { + ptr->callback(ptr->userData); + } + } + + // Thread-local + static inline ThreadExitNotifier& instance() + { + static thread_local ThreadExitNotifier notifier; + return notifier; + } + + private: + ThreadExitListener* tail; + }; +#endif +#endif + + template + struct static_is_lock_free_num { + enum { + value = 0 + }; + }; + template<> + struct static_is_lock_free_num { + enum { + value = ATOMIC_CHAR_LOCK_FREE + }; + }; + template<> + struct static_is_lock_free_num { + enum { + value = ATOMIC_SHORT_LOCK_FREE + }; + }; + template<> + struct static_is_lock_free_num { + enum { + value = ATOMIC_INT_LOCK_FREE + }; + }; + template<> + struct static_is_lock_free_num { + enum { + value = ATOMIC_LONG_LOCK_FREE + }; + }; + template<> + struct static_is_lock_free_num { + enum { + value = ATOMIC_LLONG_LOCK_FREE + }; + }; + template + struct static_is_lock_free : static_is_lock_free_num::type> { + }; + template<> + struct static_is_lock_free { + enum { + value = ATOMIC_BOOL_LOCK_FREE + }; + }; + template + struct static_is_lock_free { + enum { + value = ATOMIC_POINTER_LOCK_FREE + }; + }; + } + + + struct ProducerToken { + template + explicit ProducerToken(ConcurrentQueue &queue); + + template + explicit ProducerToken(BlockingConcurrentQueue &queue); + + ProducerToken(ProducerToken &&other) + + MOODYCAMEL_NOEXCEPT + : producer(other.producer) + { + other.producer = nullptr; + if (producer != nullptr) { + producer->token = this; + } + } + + inline ProducerToken &operator=(ProducerToken &&other) + + MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ProducerToken &other) + + MOODYCAMEL_NOEXCEPT + { + std::swap(producer, other.producer); + if (producer != nullptr) { + producer->token = this; + } + if (other.producer != nullptr) { + other.producer->token = &other; + } + } + + // A token is always valid unless: + // 1) Memory allocation failed during construction + // 2) It was moved via the move constructor + // (Note: assignment does a swap, leaving both potentially valid) + // 3) The associated queue was destroyed + // Note that if valid() returns true, that only indicates + // that the token is valid for use with a specific queue, + // but not which one; that's up to the user to track. + inline bool valid() const { return producer != nullptr; } + + ~ProducerToken() { + if (producer != nullptr) { + producer->token = nullptr; + producer->inactive.store(true, std::memory_order_release); + } + } + + // Disable copying and assignment + ProducerToken(ProducerToken const &) MOODYCAMEL_DELETE_FUNCTION; + + ProducerToken &operator=(ProducerToken const &) MOODYCAMEL_DELETE_FUNCTION; + + private: + template friend + class ConcurrentQueue; + + friend class ConcurrentQueueTests; + + protected: + details::ConcurrentQueueProducerTypelessBase *producer; + }; + + + struct ConsumerToken { + template + explicit ConsumerToken(ConcurrentQueue &q); + + template + explicit ConsumerToken(BlockingConcurrentQueue &q); + + ConsumerToken(ConsumerToken &&other) + + MOODYCAMEL_NOEXCEPT + : initialOffset(other.initialOffset), lastKnownGlobalOffset(other + .lastKnownGlobalOffset), + itemsConsumedFromCurrent(other + .itemsConsumedFromCurrent), + currentProducer(other + .currentProducer), + desiredProducer(other + .desiredProducer) + { + } + + inline ConsumerToken &operator=(ConsumerToken &&other) + + MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ConsumerToken &other) + + MOODYCAMEL_NOEXCEPT + { + std::swap(initialOffset, other.initialOffset); + std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); + std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); + std::swap(currentProducer, other.currentProducer); + std::swap(desiredProducer, other.desiredProducer); + } + + // Disable copying and assignment + ConsumerToken(ConsumerToken const &) MOODYCAMEL_DELETE_FUNCTION; + + ConsumerToken &operator=(ConsumerToken const &) MOODYCAMEL_DELETE_FUNCTION; + + private: + template friend + class ConcurrentQueue; + + friend class ConcurrentQueueTests; + + private: // but shared with ConcurrentQueue + std::uint32_t initialOffset; + std::uint32_t lastKnownGlobalOffset; + std::uint32_t itemsConsumedFromCurrent; + details::ConcurrentQueueProducerTypelessBase *currentProducer; + details::ConcurrentQueueProducerTypelessBase *desiredProducer; + }; + +// Need to forward-declare this swap because it's in a namespace. +// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces + template + inline void swap(typename ConcurrentQueue::ImplicitProducerKVP &a, + typename ConcurrentQueue::ImplicitProducerKVP &b) + + MOODYCAMEL_NOEXCEPT; + + + template + class ConcurrentQueue { + public: + typedef ::moodycamel::ProducerToken producer_token_t; + typedef ::moodycamel::ConsumerToken consumer_token_t; + + typedef typename Traits::index_t index_t; + typedef typename Traits::size_t size_t; + + static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) +#pragma warning(disable: 4309) // static_cast: Truncation of constant value +#endif + static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - + static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) + ? details::const_numeric_max::value : ( + (static_cast(Traits::MAX_SUBQUEUE_SIZE) + + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + static_assert(! + std::numeric_limits::is_signed &&std::is_integral::value, + "Traits::size_t must be an unsigned integral type"); + static_assert(! + std::numeric_limits::is_signed &&std::is_integral::value, + "Traits::index_t must be an unsigned integral type"); + static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); + static_assert((BLOCK_SIZE + > 1) && !( + BLOCK_SIZE &(BLOCK_SIZE + - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); + static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD + > 1) && !( + EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD &(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD + - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); + static_assert((EXPLICIT_INITIAL_INDEX_SIZE + > 1) && !( + EXPLICIT_INITIAL_INDEX_SIZE &(EXPLICIT_INITIAL_INDEX_SIZE + - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((IMPLICIT_INITIAL_INDEX_SIZE + > 1) && !( + IMPLICIT_INITIAL_INDEX_SIZE &(IMPLICIT_INITIAL_INDEX_SIZE + - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + == 0) || !( + INITIAL_IMPLICIT_PRODUCER_HASH_SIZE &(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); + static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); + + public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit ConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + // Track all the producers using a fully-resolved typed list for + // each kind; this makes it possible to debug them starting from + // the root queue object (otherwise wacky casts are needed that + // don't compile in the debugger's expression evaluator). + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Computes the correct amount of pre-allocated blocks for you based + // on the minimum number of elements you want available at any given + // time, and the maximum concurrent number of each type of producer. + ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + + 2 * (maxExplicitProducers + maxImplicitProducers); + populate_initial_block_list(blocks); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + // This method is not thread safe. + ~ConcurrentQueue() { + // Destroy producers + auto ptr = producerListTail.load(std::memory_order_relaxed); + while (ptr != nullptr) { + auto next = ptr->next_prod(); + if (ptr->token != nullptr) { + ptr->token->producer = nullptr; + } + destroy(ptr); + ptr = next; + } + + // Destroy implicit producer hash tables + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { + auto hash = implicitProducerHash.load(std::memory_order_relaxed); + while (hash != nullptr) { + auto prev = hash->prev; + if (prev != + nullptr) { // The last hash is part of this object and was not allocated dynamically + for (size_t i = 0; i != hash->capacity; ++i) { + hash->entries[i].~ImplicitProducerKVP(); + } + hash->~ImplicitProducerHash(); + (Traits::free)(hash); + } + hash = prev; + } + } + + // Destroy global free list + auto block = freeList.head_unsafe(); + while (block != nullptr) { + auto next = block->freeListNext.load(std::memory_order_relaxed); + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } + + // Destroy initial free list + destroy_array(initialBlockPool, initialBlockPoolSize); + } + + // Disable copying and copy assignment + ConcurrentQueue(ConcurrentQueue const &) MOODYCAMEL_DELETE_FUNCTION; + + ConcurrentQueue &operator=(ConcurrentQueue const &) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + ConcurrentQueue(ConcurrentQueue &&other) + + MOODYCAMEL_NOEXCEPT + : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), + producerCount(other + .producerCount. + load(std::memory_order_relaxed) + ), + initialBlockPoolIndex(other + .initialBlockPoolIndex. + load(std::memory_order_relaxed) + ), + initialBlockPool(other + .initialBlockPool), + initialBlockPoolSize(other + .initialBlockPoolSize), + + freeList (std::move(other + + .freeList)), + nextExplicitConsumerId(other + .nextExplicitConsumerId. + load(std::memory_order_relaxed) + ), + globalExplicitConsumerOffset(other + .globalExplicitConsumerOffset. + load(std::memory_order_relaxed) + ) + { + // Move the other one into this, and leave the other one as an empty queue + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + swap_implicit_producer_hashes(other); + + other.producerListTail.store(nullptr, std::memory_order_relaxed); + other.producerCount.store(0, std::memory_order_relaxed); + other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); + other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + + other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); + other.initialBlockPoolSize = 0; + other.initialBlockPool = nullptr; + + reown_producers(); + } + + inline ConcurrentQueue &operator=(ConcurrentQueue &&other) + + MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(ConcurrentQueue &other) + + MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + + private: + ConcurrentQueue &swap_internal(ConcurrentQueue &other) { + if (this == &other) { + return *this; + } + + details::swap_relaxed(producerListTail, other.producerListTail); + details::swap_relaxed(producerCount, other.producerCount); + details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); + std::swap(initialBlockPool, other.initialBlockPool); + std::swap(initialBlockPoolSize, other.initialBlockPoolSize); + freeList.swap(other.freeList); + details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); + details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); + + swap_implicit_producer_hashes(other); + + reown_producers(); + other.reown_producers(); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + details::swap_relaxed(explicitProducers, other.explicitProducers); + details::swap_relaxed(implicitProducers, other.implicitProducers); +#endif + + return *this; + } + + public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const &item) { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T &&item) { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const &token, T const &item) { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const &token, T &&item) { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + bool enqueue_bulk(It itemFirst, size_t count) { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool enqueue_bulk(producer_token_t const &token, It itemFirst, size_t count) { + return inner_enqueue_bulk(token, itemFirst, count); + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const &item) { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T &&item) { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const &token, T const &item) { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const &token, T &&item) { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(It itemFirst, size_t count) { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(producer_token_t const &token, It itemFirst, size_t count) { + return inner_enqueue_bulk(token, itemFirst, count); + } + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(U &item) { + // Instead of simply trying each producer in turn (which could cause needless contention on the first + // producer), we score them heuristically. + size_t nonEmptyCount = 0; + ProducerBase *best = nullptr; + size_t bestSize = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); + nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { + auto size = ptr->size_approx(); + if (size > 0) { + if (size > bestSize) { + bestSize = size; + best = ptr; + } + ++nonEmptyCount; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (nonEmptyCount > 0) { + if ((details::likely)(best->dequeue(item))) { + return true; + } + for (auto ptr = producerListTail.load(std::memory_order_acquire); + ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr != best && ptr->dequeue(item)) { + return true; + } + } + } + return false; + } + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // This differs from the try_dequeue(item) method in that this one does + // not attempt to reduce contention by interleaving the order that producer + // streams are dequeued from. So, using this method can reduce overall throughput + // under contention, but will give more predictable results in single-threaded + // consumer scenarios. This is mostly only useful for internal unit tests. + // Never allocates. Thread-safe. + template + bool try_dequeue_non_interleaved(U &item) { + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->dequeue(item)) { + return true; + } + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(consumer_token_t &token, U &item) { + // The idea is roughly as follows: + // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less + // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place + // If there's no items where you're supposed to be, keep moving until you find a producer with some items + // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it + + if (token.desiredProducer == nullptr || + token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return false; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (static_cast(token.currentProducer)->dequeue(item)) { + if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return true; + } + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + if (ptr->dequeue(item)) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = 1; + return true; + } + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(It itemFirst, size_t max) { + size_t count = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + count += ptr->dequeue_bulk(itemFirst, max - count); + if (count == max) { + break; + } + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(consumer_token_t &token, It itemFirst, size_t max) { + if (token.desiredProducer == nullptr || + token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return 0; + } + } + + size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); + if (count == max) { + if ((token.itemsConsumedFromCurrent += static_cast(max)) >= + EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return max; + } + token.itemsConsumedFromCurrent += static_cast(count); + max -= count; + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + auto dequeued = ptr->dequeue_bulk(itemFirst, max); + count += dequeued; + if (dequeued != 0) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = static_cast(dequeued); + } + if (dequeued == max) { + break; + } + max -= dequeued; + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return count; + } + + + // Attempts to dequeue from a specific producer's inner queue. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns false if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue_from_producer(producer_token_t const &producer, U &item) { + return static_cast(producer.producer)->dequeue(item); + } + + // Attempts to dequeue several elements from a specific producer's inner queue. + // Returns the number of items actually dequeued. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns 0 if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk_from_producer(producer_token_t const &producer, It itemFirst, size_t max) { + return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + size_t size_approx() const { + size_t size = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + size += ptr->size_approx(); + } + return size; + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static bool is_lock_free() { + return + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::thread_id_numeric_size_t>::value == + 2; + } + + + private: + friend struct ProducerToken; + friend struct ConsumerToken; + struct ExplicitProducer; + friend struct ExplicitProducer; + struct ImplicitProducer; + friend struct ImplicitProducer; + + friend class ConcurrentQueueTests; + + enum AllocationMode { + CanAlloc, CannotAlloc + }; + + + /////////////////////////////// + // Queue methods + /////////////////////////////// + + template + inline bool inner_enqueue(producer_token_t const &token, U &&element) { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue( + std::forward(element)); + } + + template + inline bool inner_enqueue(U &&element) { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false + : producer->ConcurrentQueue::ImplicitProducer::template enqueue( + std::forward(element)); + } + + template + inline bool inner_enqueue_bulk(producer_token_t const &token, It itemFirst, size_t count) { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk( + itemFirst, count); + } + + template + inline bool inner_enqueue_bulk(It itemFirst, size_t count) { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false + : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk( + itemFirst, count); + } + + inline bool update_current_producer_after_rotation(consumer_token_t &token) { + // Ah, there's been a rotation, figure out where we should be! + auto tail = producerListTail.load(std::memory_order_acquire); + if (token.desiredProducer == nullptr && tail == nullptr) { + return false; + } + auto prodCount = producerCount.load(std::memory_order_relaxed); + auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); + if ((details::unlikely)(token.desiredProducer == nullptr)) { + // Aha, first time we're dequeueing anything. + // Figure out our local position + // Note: offset is from start, not end, but we're traversing from end -- subtract from count first + std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); + token.desiredProducer = tail; + for (std::uint32_t i = 0; i != offset; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + } + + std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; + if (delta >= prodCount) { + delta = delta % prodCount; + } + for (std::uint32_t i = 0; i != delta; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + + token.lastKnownGlobalOffset = globalOffset; + token.currentProducer = token.desiredProducer; + token.itemsConsumedFromCurrent = 0; + return true; + } + + + /////////////////////////// + // Free list + /////////////////////////// + + template + struct FreeListNode { + FreeListNode() : freeListRefs(0), freeListNext(nullptr) {} + + std::atomic freeListRefs; + std::atomic freeListNext; + }; + + // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but + // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly + // speedy under low contention. + template // N must inherit FreeListNode or have the same fields (and initialization of them) + struct FreeList { + FreeList() : freeListHead(nullptr) {} + + FreeList(FreeList &&other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { + other.freeListHead.store(nullptr, std::memory_order_relaxed); + } + + void swap(FreeList &other) { details::swap_relaxed(freeListHead, other.freeListHead); } + + FreeList(FreeList const &) MOODYCAMEL_DELETE_FUNCTION; + + FreeList &operator=(FreeList const &) MOODYCAMEL_DELETE_FUNCTION; + + inline void add(N *node) { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to + // set it using a fetch_add + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { + // Oh look! We were the last ones referencing this node, and we know + // we want to add it to the free list, so let's do it! + add_knowing_refcount_is_zero(node); + } + } + + inline N *try_get() { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + auto head = freeListHead.load(std::memory_order_acquire); + while (head != nullptr) { + auto prevHead = head; + auto refs = head->freeListRefs.load(std::memory_order_relaxed); + if ((refs & REFS_MASK) == 0 || + !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, + std::memory_order_relaxed)) { + head = freeListHead.load(std::memory_order_acquire); + continue; + } + + // Good, reference count has been incremented (it wasn't at zero), which means we can read the + // next and not worry about it changing between now and the time we do the CAS + auto next = head->freeListNext.load(std::memory_order_relaxed); + if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, + std::memory_order_relaxed)) { + // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no + // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). + assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); + + // Decrease refcount twice, once for our ref, and once for the list's ref + head->freeListRefs.fetch_sub(2, std::memory_order_release); + return head; + } + + // OK, the head must have changed on us, but we still need to decrease the refcount we increased. + // Note that we don't need to release any memory effects, but we do need to ensure that the reference + // count decrement happens-after the CAS on the head. + refs = prevHead->freeListRefs.fetch_sub(1, std::memory_order_acq_rel); + if (refs == SHOULD_BE_ON_FREELIST + 1) { + add_knowing_refcount_is_zero(prevHead); + } + } + + return nullptr; + } + + // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) + N *head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } + + private: + inline void add_knowing_refcount_is_zero(N *node) { + // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run + // only one copy of this method per node at a time, i.e. the single thread case), then we know + // we can safely change the next pointer of the node; however, once the refcount is back above + // zero, then other threads could increase it (happens under heavy contention, when the refcount + // goes to zero in between a load and a refcount increment of a node in try_get, then back up to + // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS + // to add the node to the actual list fails, decrease the refcount and leave the add operation to + // the next thread who puts the refcount back at zero (which could be us, hence the loop). + auto head = freeListHead.load(std::memory_order_relaxed); + while (true) { + node->freeListNext.store(head, std::memory_order_relaxed); + node->freeListRefs.store(1, std::memory_order_release); + if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, + std::memory_order_relaxed)) { + // Hmm, the add failed, but we can only try again when the refcount goes back to zero + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { + continue; + } + } + return; + } + } + + private: + // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) + std::atomic freeListHead; + + static const std::uint32_t REFS_MASK = 0x7FFFFFFF; + static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; + +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugMutex mutex; +#endif + }; + + + /////////////////////////// + // Block + /////////////////////////// + + enum InnerQueueContext { + implicit_context = 0, explicit_context = 1 + }; + + struct Block { + Block() + : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), + shouldBeOnFreeList(false), dynamicallyAllocated(true) { +#ifdef MCDBGQ_TRACKMEM + owner = nullptr; +#endif + } + + template + inline bool is_empty() const { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && + BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Check flags + for (size_t i = 0; i < BLOCK_SIZE; ++i) { + if (!emptyFlags[i].load(std::memory_order_relaxed)) { + return false; + } + } + + // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } else { + // Check counter + if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); + return false; + } + } + + // Returns true if the block is now empty (does not apply in explicit context) + template + inline bool set_empty(MOODYCAMEL_MAYBE_UNUSED index_t i) { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && + BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flag + assert(!emptyFlags[BLOCK_SIZE - 1 - + static_cast(i & static_cast(BLOCK_SIZE - 1))].load( + std::memory_order_relaxed)); + emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store( + true, std::memory_order_release); + return false; + } else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); + assert(prevVal < BLOCK_SIZE); + return prevVal == BLOCK_SIZE - 1; + } + } + + // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). + // Returns true if the block is now empty (does not apply in explicit context). + template + inline bool set_many_empty(MOODYCAMEL_MAYBE_UNUSED index_t i, size_t count) { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && + BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flags + std::atomic_thread_fence(std::memory_order_release); + i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; + for (size_t j = 0; j != count; ++j) { + assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); + emptyFlags[i + j].store(true, std::memory_order_relaxed); + } + return false; + } else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); + assert(prevVal + count <= BLOCK_SIZE); + return prevVal + count == BLOCK_SIZE; + } + } + + template + inline void set_all_empty() { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && + BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set all flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(true, std::memory_order_relaxed); + } + } else { + // Reset counter + elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); + } + } + + template + inline void reset_empty() { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && + BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Reset flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(false, std::memory_order_relaxed); + } + } else { + // Reset counter + elementsCompletelyDequeued.store(0, std::memory_order_relaxed); + } + } + + inline T *operator[](index_t idx) + + MOODYCAMEL_NOEXCEPT { + return static_cast(static_cast(elements)) + + static_cast(idx & static_cast(BLOCK_SIZE - 1)); + } + + inline T const *operator[](index_t idx) const + + MOODYCAMEL_NOEXCEPT { + return static_cast(static_cast(elements)) + + static_cast(idx & static_cast(BLOCK_SIZE - 1)); + } + + private: + static_assert(std::alignment_of::value + <= sizeof(T), "The queue does not support types with an alignment greater than their size at this time"); + + MOODYCAMEL_ALIGNED_TYPE_LIKE(char[sizeof(T) * BLOCK_SIZE], T) elements; + public: + Block *next; + std::atomic elementsCompletelyDequeued; + std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; + public: + std::atomic freeListRefs; + std::atomic freeListNext; + std::atomic shouldBeOnFreeList; + bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' + +#ifdef MCDBGQ_TRACKMEM + void* owner; +#endif + }; + + static_assert(std::alignment_of::value + >= std::alignment_of::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping"); + + +#ifdef MCDBGQ_TRACKMEM + public: + struct MemStats; +private: +#endif + + /////////////////////////// + // Producer base + /////////////////////////// + + struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase { + ProducerBase(ConcurrentQueue *parent_, bool isExplicit_) : + tailIndex(0), + headIndex(0), + dequeueOptimisticCount(0), + dequeueOvercommit(0), + tailBlock(nullptr), + isExplicit(isExplicit_), + parent(parent_) { + } + + virtual ~ProducerBase() {} + + template + inline bool dequeue(U &element) { + if (isExplicit) { + return static_cast(this)->dequeue(element); + } else { + return static_cast(this)->dequeue(element); + } + } + + template + inline size_t dequeue_bulk(It &itemFirst, size_t max) { + if (isExplicit) { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } else { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + } + + inline ProducerBase *next_prod() const { return static_cast(next); } + + inline size_t size_approx() const { + auto tail = tailIndex.load(std::memory_order_relaxed); + auto head = headIndex.load(std::memory_order_relaxed); + return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; + } + + inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } + + protected: + std::atomic tailIndex; // Where to enqueue to next + std::atomic headIndex; // Where to dequeue from next + + std::atomic dequeueOptimisticCount; + std::atomic dequeueOvercommit; + + Block *tailBlock; + + public: + bool isExplicit; + ConcurrentQueue *parent; + + protected: +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + /////////////////////////// + // Explicit queue + /////////////////////////// + + struct ExplicitProducer : public ProducerBase { + explicit ExplicitProducer(ConcurrentQueue *parent_) : + ProducerBase(parent_, true), + blockIndex(nullptr), + pr_blockIndexSlotsUsed(0), + pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), + pr_blockIndexFront(0), + pr_blockIndexEntries(nullptr), + pr_blockIndexRaw(nullptr) { + size_t poolBasedIndexSize = details::ceil_to_pow_2(parent_->initialBlockPoolSize) >> 1; + if (poolBasedIndexSize > pr_blockIndexSize) { + pr_blockIndexSize = poolBasedIndexSize; + } + + new_block_index( + 0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE + } + + ~ExplicitProducer() { + // Destruct any elements not yet dequeued. + // Since we're in the destructor, we can assume all elements + // are either completely dequeued or completely not (no halfways). + if (this->tailBlock != nullptr) { // Note this means there must be a block index too + // First find the block that's partially dequeued, if any + Block *halfDequeuedBlock = nullptr; + if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { + // The head's not on a block boundary, meaning a block somewhere is partially dequeued + // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) + size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); + while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, + this->headIndex.load(std::memory_order_relaxed))) { + i = (i + 1) & (pr_blockIndexSize - 1); + } + assert(details::circular_less_than(pr_blockIndexEntries[i].base, + this->headIndex.load(std::memory_order_relaxed))); + halfDequeuedBlock = pr_blockIndexEntries[i].block; + } + + // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) + auto block = this->tailBlock; + do { + block = block->next; + if (block->ConcurrentQueue::Block::template is_empty()) { + continue; + } + + size_t i = 0; // Offset into block + if (block == halfDequeuedBlock) { + i = static_cast(this->headIndex.load(std::memory_order_relaxed) & + static_cast(BLOCK_SIZE - 1)); + } + + // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index + auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & + static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE + : static_cast( + this->tailIndex.load(std::memory_order_relaxed) & + static_cast(BLOCK_SIZE - 1)); + while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { + (*block)[i++]->~T(); + } + } while (block != this->tailBlock); + } + + // Destroy all blocks that we own + if (this->tailBlock != nullptr) { + auto block = this->tailBlock; + do { + auto nextBlock = block->next; + if (block->dynamicallyAllocated) { + destroy(block); + } else { + this->parent->add_block_to_free_list(block); + } + block = nextBlock; + } while (block != this->tailBlock); + } + + // Destroy the block indices + auto header = static_cast(pr_blockIndexRaw); + while (header != nullptr) { + auto prev = static_cast(header->prev); + header->~BlockIndexHeader(); + (Traits::free)(header); + header = prev; + } + } + + template + inline bool enqueue(U &&element) { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto startBlock = this->tailBlock; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + if (this->tailBlock != nullptr && + this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + // We can re-use the block ahead of us, it's empty! + this->tailBlock = this->tailBlock->next; + this->tailBlock->ConcurrentQueue::Block::template reset_empty(); + + // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the + // last block from it first -- except instead of removing then adding, we can just overwrite). + // Note that there must be a valid block index here, since even if allocation failed in the ctor, + // it would have been re-attempted when adding the first block to the queue; since there is such + // a block, a block index must have been successfully allocated. + } else { + // Whatever head value we see here is >= the last value we saw here (relatively), + // and <= its current value. Since we have the most recent tail, the head must be + // <= to it. + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) + || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && + (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + // We can't enqueue in another block because there's not enough leeway -- the + // tail could surpass the head by the time the block fills up! (Or we'll exceed + // the size limit, if the second part of the condition was true.) + return false; + } + // We're going to need a new block; check that the block index has room + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { + // Hmm, the circular block index is already full -- we'll need + // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if + // the initial allocation failed in the constructor. + + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } else if (!new_block_index(pr_blockIndexSlotsUsed)) { + return false; + } + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + ++pr_blockIndexSlotsUsed; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new(static_cast(nullptr)) T( + std::forward(element)))) { + // The constructor may throw. We want the element not to appear in the queue in + // that case (without corrupting the queue): + MOODYCAMEL_TRY { + new((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + // Revert change to the current block, but leave the new block available + // for next time + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; + MOODYCAMEL_RETHROW; + } + } else { + (void) startBlock; + (void) originalBlockIndexSlotsUsed; + } + + // Add block to block index + auto &entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, + std::memory_order_release); + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new(static_cast(nullptr)) T( + std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U &element) { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than( + this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + // Might be something to dequeue, let's give it a try + + // Note that this if is purely for performance purposes in the common case when the queue is + // empty and the values are eventually consistent -- we may enter here spuriously. + + // Note that whatever the values of overcommit and tail are, they are not going to change (unless we + // change them) and must be the same value at this point (inside the if) as when the if condition was + // evaluated. + + // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. + // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in + // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). + // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all + // read-modify-write operations are guaranteed to work on the latest value in the modification order), but + // unfortunately that can't be shown to be correct using only the C++11 standard. + // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case + std::atomic_thread_fence(std::memory_order_acquire); + + // Increment optimistic counter, then check if it went over the boundary + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + + // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever + // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now + // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon + // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. + // However, we can't assert this since both dequeueOptimisticCount and dequeueOvercommit may (independently) + // overflow; in such a case, though, the logic still holds since the difference between the two is maintained. + + // Note that we reload tail here in case it changed; it will be the same value as before or greater, since + // this load is sequenced after (happens after) the earlier load above. This is supported by read-read + // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + // Guaranteed to be at least one element to dequeue! + + // Get the index. Note that since there's guaranteed to be at least one element, this + // will never exceed tail. We need to do an acquire-release fence here since it's possible + // that whatever condition got us to this point was for an earlier enqueued element (that + // we already see the memory effects for), but that by the time we increment somebody else + // has incremented it, and we need to see the memory effects for *that* element, which is + // in such a case is necessarily visible on the thread that incremented it in the first + // place with the more current condition (they must have acquired a tail that is at least + // as recent). + auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + + // Determine which block the element is in + + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + // We need to be careful here about subtracting and dividing because of index wrap-around. + // When an index wraps, we need to preserve the sign of the offset when dividing it by the + // block size (in order to get a correct signed block count offset in all cases): + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast( + static_cast::type>(blockBaseIndex - headBase) / + BLOCK_SIZE); + auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & + (localBlockIndex->size - 1)].block; + + // Dequeue + auto &el = *((*block)[index]); + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T &&, element = std::move(el))) { + // Make sure the element is still fully dequeued and destroyed even if the assignment + // throws + struct Guard { + Block *block; + index_t index; + + ~Guard() { + (*block)[index]->~T(); + block->ConcurrentQueue::Block::template set_empty(index); + } + } guard = {block, index}; + + element = std::move(el); // NOLINT + } else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + block->ConcurrentQueue::Block::template set_empty(index); + } + + return true; + } else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(1, + std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write + } + } + + return false; + } + + template + bool MOODYCAMEL_NO_TSAN enqueue_bulk(It itemFirst, size_t count) { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + auto originalBlockIndexFront = pr_blockIndexFront; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + + Block *firstAllocatedBlock = nullptr; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - + ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { + // Allocate as many blocks as possible from ahead + while (blockBaseDiff > 0 && this->tailBlock != nullptr && + this->tailBlock->next != firstAllocatedBlock && + this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + this->tailBlock = this->tailBlock->next; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + auto &entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Now allocate as many blocks as necessary from the block pool + while (blockBaseDiff > 0) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || + (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && + (MAX_SUBQUEUE_SIZE == 0 || + MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } else if (full || !new_block_index(originalBlockIndexSlotsUsed)) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + + // pr_blockIndexFront is updated inside new_block_index, so we need to + // update our fallback value too (since we keep the new index even if we + // later fail) + originalBlockIndexFront = originalBlockIndexSlotsUsed; + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template set_all_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + ++pr_blockIndexSlotsUsed; + + auto &entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and + // publish the new block index front + auto block = firstAllocatedBlock; + while (true) { + block->ConcurrentQueue::Block::template reset_empty(); + if (block == this->tailBlock) { + break; + } + block = block->next; + } + + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), + new(static_cast(nullptr)) T( + details::deref_noexcept(itemFirst)))) { + blockIndex.load(std::memory_order_relaxed)->front.store( + (pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + auto endBlock = this->tailBlock; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || + count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), + new(static_cast(nullptr)) T( + details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + // Must use copy constructor even if move constructor is available + // because we may have to revert if there's an exception. + // Sorry about the horrible templated next line, but it was the only way + // to disable moving *at compile time*, which is important because a type + // may only define a (noexcept) move constructor, and so calls to the + // cctor will not compile, even if they are in an if branch that will never + // be executed + new((*this->tailBlock)[currentTailIndex]) T( + details::nomove_if(nullptr)) T( + details::deref_noexcept( + itemFirst)))>::eval( + *itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + // Oh dear, an exception's been thrown -- destroy the elements that + // were enqueued so far and revert the entire bulk operation (we'll keep + // any allocated blocks in our linked list for later, though). + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), + new(static_cast(nullptr)) T( + details::deref_noexcept(itemFirst)))) { + if (firstAllocatedBlock != nullptr) + blockIndex.load(std::memory_order_relaxed)->front.store( + (pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + size_t dequeue_bulk(It &itemFirst, size_t max) { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - + (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - + overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, + std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Determine which block the first element is in + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast( + static_cast::type>(firstBlockBaseIndex - headBase) / + BLOCK_SIZE); + auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); + + // Iterate the blocks and dequeue + auto index = firstIndex; + do { + auto firstIndexInBlock = index; + index_t endIndex = + (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than( + firstIndex + static_cast(actualCount), endIndex) ? firstIndex + + static_cast(actualCount) + : endIndex; + auto block = localBlockIndex->entries[indexIndex].block; + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T &&, details::deref_noexcept(itemFirst) = std::move( + (*(*block)[index])))) { + while (index != endIndex) { + auto &el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto &el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + // It's too late to revert the dequeue, but we can make sure that all + // the dequeued objects are properly destroyed and the block index + // (and empty count) are properly updated before we propagate the exception + do { + block = localBlockIndex->entries[indexIndex].block; + while (index != endIndex) { + (*block)[index++]->~T(); + } + block->ConcurrentQueue::Block::template set_many_empty( + firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + + firstIndexInBlock = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than( + firstIndex + static_cast(actualCount), endIndex) ? firstIndex + + static_cast(actualCount) + : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, + static_cast( + endIndex - + firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + struct BlockIndexEntry { + index_t base; + Block *block; + }; + + struct BlockIndexHeader { + size_t size; + std::atomic front; // Current slot (not next, like pr_blockIndexFront) + BlockIndexEntry *entries; + void *prev; + }; + + + bool new_block_index(size_t numberOfFilledSlotsToExpose) { + auto prevBlockSizeMask = pr_blockIndexSize - 1; + + // Create the new block + pr_blockIndexSize <<= 1; + auto newRawPtr = static_cast((Traits::malloc)( + sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + + sizeof(BlockIndexEntry) * pr_blockIndexSize)); + if (newRawPtr == nullptr) { + pr_blockIndexSize >>= 1; // Reset to allow graceful retry + return false; + } + + auto newBlockIndexEntries = reinterpret_cast(details::align_for( + newRawPtr + sizeof(BlockIndexHeader))); + + // Copy in all the old indices, if any + size_t j = 0; + if (pr_blockIndexSlotsUsed != 0) { + auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; + do { + newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; + i = (i + 1) & prevBlockSizeMask; + } while (i != pr_blockIndexFront); + } + + // Update everything + auto header = new(newRawPtr) BlockIndexHeader; + header->size = pr_blockIndexSize; + header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); + header->entries = newBlockIndexEntries; + header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later + + pr_blockIndexFront = j; + pr_blockIndexEntries = newBlockIndexEntries; + pr_blockIndexRaw = newRawPtr; + blockIndex.store(header, std::memory_order_release); + + return true; + } + + private: + std::atomic blockIndex; + + // To be used by producer only -- consumer must use the ones in referenced by blockIndex + size_t pr_blockIndexSlotsUsed; + size_t pr_blockIndexSize; + size_t pr_blockIndexFront; // Next slot (not current) + BlockIndexEntry *pr_blockIndexEntries; + void *pr_blockIndexRaw; + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ExplicitProducer* nextExplicitProducer; + private: +#endif + +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Implicit queue + ////////////////////////////////// + + struct ImplicitProducer : public ProducerBase { + ImplicitProducer(ConcurrentQueue *parent_) : + ProducerBase(parent_, false), + nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), + blockIndex(nullptr) { + new_block_index(); + } + + ~ImplicitProducer() { + // Note that since we're in the destructor we can assume that all enqueue/dequeue operations + // completed already; this means that all undequeued elements are placed contiguously across + // contiguous blocks, and that only the first and last remaining blocks can be only partially + // empty (all other remaining blocks must be completely full). + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + // Unregister ourselves for thread termination notification + if (!this->inactive.load(std::memory_order_relaxed)) { + details::ThreadExitNotifier::unsubscribe(&threadExitListener); + } +#endif + + // Destroy all remaining elements! + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto index = this->headIndex.load(std::memory_order_relaxed); + Block *block = nullptr; + assert(index == tail || details::circular_less_than(index, tail)); + bool forceFreeLastBlock = + index != tail; // If we enter the loop, then the last (tail) block will not be freed + while (index != tail) { + if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { + if (block != nullptr) { + // Free the old block + this->parent->add_block_to_free_list(block); + } + + block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); + } + + ((*block)[index])->~T(); + ++index; + } + // Even if the queue is empty, there's still one block that's not on the free list + // (unless the head index reached the end of it, in which case the tail will be poised + // to create a new block). + if (this->tailBlock != nullptr && + (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0)) { + this->parent->add_block_to_free_list(this->tailBlock); + } + + // Destroy block index + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + if (localBlockIndex != nullptr) { + for (size_t i = 0; i != localBlockIndex->capacity; ++i) { + localBlockIndex->index[i]->~BlockIndexEntry(); + } + do { + auto prev = localBlockIndex->prev; + localBlockIndex->~BlockIndexHeader(); + (Traits::free)(localBlockIndex); + localBlockIndex = prev; + } while (localBlockIndex != nullptr); + } + } + + template + inline bool enqueue(U &&element) { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || + (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && + (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + return false; + } +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Find out where we'll be inserting this block in the block index + BlockIndexEntry *idxEntry; + if (!insert_block_index_entry(idxEntry, currentTailIndex)) { + return false; + } + + // Get ahold of a new block + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new(static_cast(nullptr)) T( + std::forward(element)))) { + // May throw, try to insert now before we publish the fact that we have this new block + MOODYCAMEL_TRY { + new((*newBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(newBlock); + MOODYCAMEL_RETHROW; + } + } + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + this->tailBlock = newBlock; + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new(static_cast(nullptr)) T( + std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U &element) { + // See ExplicitProducer::dequeue for rationale and explanation + index_t tail = this->tailIndex.load(std::memory_order_relaxed); + index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than( + this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + std::atomic_thread_fence(std::memory_order_acquire); + + index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + // Determine which block the element is in + auto entry = get_block_index_entry_for_index(index); + + // Dequeue + auto block = entry->value.load(std::memory_order_relaxed); + auto &el = *((*block)[index]); + + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T &&, element = std::move(el))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + // Note: Acquiring the mutex with every dequeue instead of only when a block + // is released is very sub-optimal, but it is, after all, purely debug code. + debug::DebugLock lock(producer->mutex); +#endif + struct Guard { + Block *block; + index_t index; + BlockIndexEntry *entry; + ConcurrentQueue *parent; + + ~Guard() { + (*block)[index]->~T(); + if (block->ConcurrentQueue::Block::template set_empty(index)) { + entry->value.store(nullptr, std::memory_order_relaxed); + parent->add_block_to_free_list(block); + } + } + } guard = {block, index, entry, this->parent}; + + element = std::move(el); // NOLINT + } else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + + if (block->ConcurrentQueue::Block::template set_empty(index)) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Add the block back into the global free pool (and remove from block index) + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + } + + return true; + } else { + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); + } + } + + return false; + } + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4706) // assignment within conditional expression +#endif + + template + bool enqueue_bulk(It itemFirst, size_t count) { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + + // Note that the tailBlock we start off with may not be owned by us any more; + // this happens if it was filled up exactly to the top (setting tailIndex to + // the first index of the next block which is not yet allocated), then dequeued + // completely (putting it on the free list) before we enqueue again. + + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + Block *firstAllocatedBlock = nullptr; + auto endBlock = this->tailBlock; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - + ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + do { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + // Find out where we'll be inserting this block in the block index + BlockIndexEntry *idxEntry = nullptr; // initialization here unnecessary but compiler can't always tell + Block *newBlock; + bool indexInserted = false; + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || + (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && + (MAX_SUBQUEUE_SIZE == 0 || + MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + + if (full || + !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || + (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == + nullptr) { + // Index allocation or block allocation failed; revert any other allocations + // and index insertions done so far for this operation + if (indexInserted) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + } + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + newBlock->next = nullptr; + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + // Store the chain of blocks so that we can undo if later allocations fail, + // and so that we can find the blocks when we do the actual enqueueing + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || + firstAllocatedBlock != nullptr) { + assert(this->tailBlock != nullptr); + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + endBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; + } while (blockBaseDiff > 0); + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || + count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), + new(static_cast(nullptr)) T( + details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + new((*this->tailBlock)[currentTailIndex]) T( + details::nomove_if(nullptr)) T( + details::deref_noexcept( + itemFirst)))>::eval( + *itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + auto idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template + size_t dequeue_bulk(It &itemFirst, size_t max) { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - + (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - + overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, + std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Iterate the blocks and dequeue + auto index = firstIndex; + BlockIndexHeader *localBlockIndex; + auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); + do { + auto blockStartIndex = index; + index_t endIndex = + (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than( + firstIndex + static_cast(actualCount), endIndex) ? firstIndex + + static_cast(actualCount) + : endIndex; + + auto entry = localBlockIndex->index[indexIndex]; + auto block = entry->value.load(std::memory_order_relaxed); + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T &&, details::deref_noexcept(itemFirst) = std::move( + (*(*block)[index])))) { + while (index != endIndex) { + auto &el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto &el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + do { + entry = localBlockIndex->index[indexIndex]; + block = entry->value.load(std::memory_order_relaxed); + while (index != endIndex) { + (*block)[index++]->~T(); + } + + if (block->ConcurrentQueue::Block::template set_many_empty( + blockStartIndex, static_cast(endIndex - blockStartIndex))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + entry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(block); + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + + blockStartIndex = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than( + firstIndex + static_cast(actualCount), endIndex) ? firstIndex + + static_cast(actualCount) + : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + if (block->ConcurrentQueue::Block::template set_many_empty( + blockStartIndex, static_cast(endIndex - blockStartIndex))) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Note that the set_many_empty above did a release, meaning that anybody who acquires the block + // we're about to free can use it safely since our writes (and reads!) will have happened-before then. + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } else { + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + // The block size must be > 1, so any number with the low bit set is an invalid block base index + static const index_t INVALID_BLOCK_BASE = 1; + + struct BlockIndexEntry { + std::atomic key; + std::atomic value; + }; + + struct BlockIndexHeader { + size_t capacity; + std::atomic tail; + BlockIndexEntry *entries; + BlockIndexEntry **index; + BlockIndexHeader *prev; + }; + + template + inline bool insert_block_index_entry(BlockIndexEntry *&idxEntry, index_t blockStartIndex) { + auto localBlockIndex = blockIndex.load( + std::memory_order_relaxed); // We're the only writer thread, relaxed is OK + if (localBlockIndex == nullptr) { + return false; // this can happen if new_block_index failed in the constructor + } + size_t newTail = + (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || + idxEntry->value.load(std::memory_order_relaxed) == nullptr) { + + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + // No room in the old block index, try to allocate another one! + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } else if (!new_block_index()) { + return false; + } + localBlockIndex = blockIndex.load(std::memory_order_relaxed); + newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + inline void rewind_block_index_tail() { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + localBlockIndex->tail.store( + (localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), + std::memory_order_relaxed); + } + + inline BlockIndexEntry *get_block_index_entry_for_index(index_t index) const { + BlockIndexHeader *localBlockIndex; + auto idx = get_block_index_index_for_index(index, localBlockIndex); + return localBlockIndex->index[idx]; + } + + inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader *&localBlockIndex) const { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + index &= ~static_cast(BLOCK_SIZE - 1); + localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto tail = localBlockIndex->tail.load(std::memory_order_acquire); + auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); + assert(tailBase != INVALID_BLOCK_BASE); + // Note: Must use division instead of shift because the index may wrap around, causing a negative + // offset, whose negativity we want to preserve + auto offset = static_cast( + static_cast::type>(index - tailBase) / BLOCK_SIZE); + size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); + assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && + localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); + return idx; + } + + bool new_block_index() { + auto prev = blockIndex.load(std::memory_order_relaxed); + size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; + auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; + auto raw = static_cast((Traits::malloc)( + sizeof(BlockIndexHeader) + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + + std::alignment_of::value - 1 + + sizeof(BlockIndexEntry *) * nextBlockIndexCapacity)); + if (raw == nullptr) { + return false; + } + + auto header = new(raw) BlockIndexHeader; + auto entries = reinterpret_cast(details::align_for( + raw + sizeof(BlockIndexHeader))); + auto index = reinterpret_cast(details::align_for( + reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); + if (prev != nullptr) { + auto prevTail = prev->tail.load(std::memory_order_relaxed); + auto prevPos = prevTail; + size_t i = 0; + do { + prevPos = (prevPos + 1) & (prev->capacity - 1); + index[i++] = prev->index[prevPos]; + } while (prevPos != prevTail); + assert(i == prevCapacity); + } + for (size_t i = 0; i != entryCount; ++i) { + new(entries + i) BlockIndexEntry; + entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); + index[prevCapacity + i] = entries + i; + } + header->prev = prev; + header->entries = entries; + header->index = index; + header->capacity = nextBlockIndexCapacity; + header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); + + blockIndex.store(header, std::memory_order_release); + + nextBlockIndexCapacity <<= 1; + + return true; + } + + private: + size_t nextBlockIndexCapacity; + std::atomic blockIndex; + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + public: + details::ThreadExitListener threadExitListener; + private: +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ImplicitProducer* nextImplicitProducer; + private: +#endif + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + mutable debug::DebugMutex mutex; +#endif +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Block pool manipulation + ////////////////////////////////// + + void populate_initial_block_list(size_t blockCount) { + initialBlockPoolSize = blockCount; + if (initialBlockPoolSize == 0) { + initialBlockPool = nullptr; + return; + } + + initialBlockPool = create_array(blockCount); + if (initialBlockPool == nullptr) { + initialBlockPoolSize = 0; + } + for (size_t i = 0; i < initialBlockPoolSize; ++i) { + initialBlockPool[i].dynamicallyAllocated = false; + } + } + + inline Block *try_get_block_from_initial_pool() { + if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { + return nullptr; + } + + auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); + + return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; + } + + inline void add_block_to_free_list(Block *block) { +#ifdef MCDBGQ_TRACKMEM + block->owner = nullptr; +#endif + freeList.add(block); + } + + inline void add_blocks_to_free_list(Block *block) { + while (block != nullptr) { + auto next = block->next; + add_block_to_free_list(block); + block = next; + } + } + + inline Block *try_get_block_from_free_list() { + return freeList.try_get(); + } + + // Gets a free block from one of the memory pools, or allocates a new one (if applicable) + template + Block *requisition_block() { + auto block = try_get_block_from_initial_pool(); + if (block != nullptr) { + return block; + } + + block = try_get_block_from_free_list(); + if (block != nullptr) { + return block; + } + + MOODYCAMEL_CONSTEXPR_IF (canAlloc == CanAlloc) { + return create(); + } else { + return nullptr; + } + } + + +#ifdef MCDBGQ_TRACKMEM + public: + struct MemStats { + size_t allocatedBlocks; + size_t usedBlocks; + size_t freeBlocks; + size_t ownedBlocksExplicit; + size_t ownedBlocksImplicit; + size_t implicitProducers; + size_t explicitProducers; + size_t elementsEnqueued; + size_t blockClassBytes; + size_t queueClassBytes; + size_t implicitBlockIndexBytes; + size_t explicitBlockIndexBytes; + + friend class ConcurrentQueue; + + private: + static MemStats getFor(ConcurrentQueue* q) + { + MemStats stats = { 0 }; + + stats.elementsEnqueued = q->size_approx(); + + auto block = q->freeList.head_unsafe(); + while (block != nullptr) { + ++stats.allocatedBlocks; + ++stats.freeBlocks; + block = block->freeListNext.load(std::memory_order_relaxed); + } + + for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + bool implicit = dynamic_cast(ptr) != nullptr; + stats.implicitProducers += implicit ? 1 : 0; + stats.explicitProducers += implicit ? 0 : 1; + + if (implicit) { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ImplicitProducer); + auto head = prod->headIndex.load(std::memory_order_relaxed); + auto tail = prod->tailIndex.load(std::memory_order_relaxed); + auto hash = prod->blockIndex.load(std::memory_order_relaxed); + if (hash != nullptr) { + for (size_t i = 0; i != hash->capacity; ++i) { + if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { + ++stats.allocatedBlocks; + ++stats.ownedBlocksImplicit; + } + } + stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); + for (; hash != nullptr; hash = hash->prev) { + stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); + } + } + for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { + //auto block = prod->get_block_index_entry_for_index(head); + ++stats.usedBlocks; + } + } + else { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ExplicitProducer); + auto tailBlock = prod->tailBlock; + bool wasNonEmpty = false; + if (tailBlock != nullptr) { + auto block = tailBlock; + do { + ++stats.allocatedBlocks; + if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { + ++stats.usedBlocks; + wasNonEmpty = wasNonEmpty || block != tailBlock; + } + ++stats.ownedBlocksExplicit; + block = block->next; + } while (block != tailBlock); + } + auto index = prod->blockIndex.load(std::memory_order_relaxed); + while (index != nullptr) { + stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); + index = static_cast(index->prev); + } + } + } + + auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); + stats.allocatedBlocks += freeOnInitialPool; + stats.freeBlocks += freeOnInitialPool; + + stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; + stats.queueClassBytes += sizeof(ConcurrentQueue); + + return stats; + } + }; + + // For debugging only. Not thread-safe. + MemStats getMemStats() + { + return MemStats::getFor(this); + } + private: + friend struct MemStats; +#endif + + + ////////////////////////////////// + // Producer list manipulation + ////////////////////////////////// + + ProducerBase *recycle_or_create_producer(bool isExplicit) { + bool recycled; + return recycle_or_create_producer(isExplicit, recycled); + } + + ProducerBase *recycle_or_create_producer(bool isExplicit, bool &recycled) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + // Try to re-use one first + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { + bool expected = true; + if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, + std::memory_order_relaxed)) { + // We caught one! It's been marked as activated, the caller can have it + recycled = true; + return ptr; + } + } + } + + recycled = false; + return add_producer( + isExplicit ? static_cast(create(this)) : create( + this)); + } + + ProducerBase *add_producer(ProducerBase *producer) { + // Handle failed memory allocation + if (producer == nullptr) { + return nullptr; + } + + producerCount.fetch_add(1, std::memory_order_relaxed); + + // Add it to the lock-free list + auto prevTail = producerListTail.load(std::memory_order_relaxed); + do { + producer->next = prevTail; + } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, + std::memory_order_relaxed)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + if (producer->isExplicit) { + auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextExplicitProducer = prevTailExplicit; + } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } + else { + auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextImplicitProducer = prevTailImplicit; + } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } +#endif + + return producer; + } + + void reown_producers() { + // After another instance is moved-into/swapped-with this one, all the + // producers we stole still think their parents are the other queue. + // So fix them up! + for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { + ptr->parent = this; + } + } + + + ////////////////////////////////// + // Implicit producer hash + ////////////////////////////////// + + struct ImplicitProducerKVP { + std::atomic key; + ImplicitProducer *value; // No need for atomicity since it's only read by the thread that sets it in the first place + + ImplicitProducerKVP() : value(nullptr) {} + + ImplicitProducerKVP(ImplicitProducerKVP &&other) + + MOODYCAMEL_NOEXCEPT + { + key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); + value = other.value; + } + + inline ImplicitProducerKVP &operator=(ImplicitProducerKVP &&other) + + MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + inline void swap(ImplicitProducerKVP &other) + + MOODYCAMEL_NOEXCEPT + { + if (this != &other) { + details::swap_relaxed(key, other.key); + std::swap(value, other.value); + } + } + }; + + template + friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP &, + typename ConcurrentQueue::ImplicitProducerKVP &) + + MOODYCAMEL_NOEXCEPT; + + struct ImplicitProducerHash { + size_t capacity; + ImplicitProducerKVP *entries; + ImplicitProducerHash *prev; + }; + + inline void populate_initial_implicit_producer_hash() { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } else { + implicitProducerHashCount.store(0, std::memory_order_relaxed); + auto hash = &initialImplicitProducerHash; + hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + hash->entries = &initialImplicitProducerHashEntries[0]; + for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { + initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, + std::memory_order_relaxed); + } + hash->prev = nullptr; + implicitProducerHash.store(hash, std::memory_order_relaxed); + } + } + + void swap_implicit_producer_hashes(ConcurrentQueue &other) { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } else { + // Swap (assumes our implicit producer hash is initialized) + initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); + initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; + other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; + + details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); + + details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); + if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { + implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); + } else { + ImplicitProducerHash *hash; + for (hash = implicitProducerHash.load(std::memory_order_relaxed); + hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &initialImplicitProducerHash; + } + if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { + other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); + } else { + ImplicitProducerHash *hash; + for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); + hash->prev != &initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &other.initialImplicitProducerHash; + } + } + } + + // Only fails (returns nullptr) if memory allocation fails + ImplicitProducer *get_or_add_implicit_producer() { + // Note that since the data is essentially thread-local (key is thread ID), + // there's a reduced need for fences (memory ordering is already consistent + // for any individual thread), except for the current table itself. + + // Start by looking for the thread ID in the current and all previous hash tables. + // If it's not found, it must not be in there yet, since this same thread would + // have added it previously to one of the tables that we traversed. + + // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + + auto mainHash = implicitProducerHash.load(std::memory_order_acquire); + assert(mainHash != nullptr); // silence clang-tidy and MSVC warnings (hash cannot be null) + for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { + // Look for the id in this hash + auto index = hashedId; + while (true) { // Not an infinite loop because at least one slot is free in the hash table + index &= hash->capacity - 1; + + auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + // Found it! If we had to search several hashes deep, though, we should lazily add it + // to the current main hash table to avoid the extended search next time. + // Note there's guaranteed to be room in the current hash table since every subsequent + // table implicitly reserves space for all previous tables (there's only one + // implicitProducerHashCount). + auto value = hash->entries[index].value; + if (hash != mainHash) { + index = hashedId; + while (true) { + index &= mainHash->capacity - 1; + probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, std::memory_order_relaxed)) || + (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire, std::memory_order_acquire))) { +#else + if ((probedKey == empty && + mainHash->entries[index].key.compare_exchange_strong(empty, id, + std::memory_order_relaxed, + std::memory_order_relaxed))) { +#endif + mainHash->entries[index].value = value; + break; + } + ++index; + } + } + + return value; + } + if (probedKey == details::invalid_thread_id) { + break; // Not in this hash table + } + ++index; + } + } + + // Insert! + auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); + while (true) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + if (newCount >= (mainHash->capacity >> 1) && + !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { + // We've acquired the resize lock, try to allocate a bigger hash table. + // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when + // we reload implicitProducerHash it must be the most recent version (it only gets changed within this + // locked block). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + if (newCount >= (mainHash->capacity >> 1)) { + auto newCapacity = mainHash->capacity << 1; + while (newCount >= (newCapacity >> 1)) { + newCapacity <<= 1; + } + auto raw = static_cast((Traits::malloc)( + sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + + sizeof(ImplicitProducerKVP) * newCapacity)); + if (raw == nullptr) { + // Allocation failed + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + return nullptr; + } + + auto newHash = new(raw) ImplicitProducerHash; + newHash->capacity = static_cast(newCapacity); + newHash->entries = reinterpret_cast(details::align_for( + raw + sizeof(ImplicitProducerHash))); + for (size_t i = 0; i != newCapacity; ++i) { + new(newHash->entries + i) ImplicitProducerKVP; + newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + newHash->prev = mainHash; + implicitProducerHash.store(newHash, std::memory_order_release); + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + mainHash = newHash; + } else { + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + } + } + + // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table + // to finish being allocated by another thread (and if we just finished allocating above, the condition will + // always be true) + if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { + bool recycled; + auto producer = static_cast(recycle_or_create_producer(false, recycled)); + if (producer == nullptr) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + return nullptr; + } + if (recycled) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; + producer->threadExitListener.userData = producer; + details::ThreadExitNotifier::subscribe(&producer->threadExitListener); +#endif + + auto index = hashedId; + while (true) { + index &= mainHash->capacity - 1; + auto probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); + + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, std::memory_order_relaxed)) || + (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire, std::memory_order_acquire))) { +#else + if ((probedKey == empty && + mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, + std::memory_order_relaxed))) { +#endif + mainHash->entries[index].value = producer; + break; + } + ++index; + } + return producer; + } + + // Hmm, the old hash is quite full and somebody else is busy allocating a new one. + // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, + // we try to allocate ourselves). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + } + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + void implicit_producer_thread_exited(ImplicitProducer* producer) + { + // Remove from thread exit listeners + details::ThreadExitNotifier::unsubscribe(&producer->threadExitListener); + + // Remove from hash +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + auto hash = implicitProducerHash.load(std::memory_order_acquire); + assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + details::thread_id_t probedKey; + + // We need to traverse all the hashes just in case other threads aren't on the current one yet and are + // trying to add an entry thinking there's a free slot (because they reused a producer) + for (; hash != nullptr; hash = hash->prev) { + auto index = hashedId; + do { + index &= hash->capacity - 1; + probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + hash->entries[index].key.store(details::invalid_thread_id2, std::memory_order_release); + break; + } + ++index; + } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place + } + + // Mark the queue as being recyclable + producer->inactive.store(true, std::memory_order_release); + } + + static void implicit_producer_thread_exited_callback(void* userData) + { + auto producer = static_cast(userData); + auto queue = producer->parent; + queue->implicit_producer_thread_exited(producer); + } +#endif + + ////////////////////////////////// + // Utility functions + ////////////////////////////////// + + template + static inline void *aligned_malloc(size_t size) { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= + std::alignment_of::value)return (Traits::malloc)(size); + else { + size_t alignment = std::alignment_of::value; + void *raw = (Traits::malloc)(size + alignment - 1 + sizeof(void *)); + if (!raw) + return nullptr; + char *ptr = details::align_for(reinterpret_cast(raw) + sizeof(void *)); + *(reinterpret_cast(ptr) - 1) = raw; + return ptr; + } + } + + template + static inline void aligned_free(void *ptr) { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= + std::alignment_of::value)return (Traits::free)(ptr); + else + (Traits::free)(ptr ? *(reinterpret_cast(ptr) - 1) : nullptr); + } + + template + static inline U *create_array(size_t count) { + assert(count > 0); + U *p = static_cast(aligned_malloc(sizeof(U) * count)); + if (p == nullptr) + return nullptr; + + for (size_t i = 0; i != count; ++i) + new(p + i) U(); + return p; + } + + template + static inline void destroy_array(U *p, size_t count) { + if (p != nullptr) { + assert(count > 0); + for (size_t i = count; i != 0;) + (p + --i)->~U(); + } + aligned_free(p); + } + + template + static inline U *create() { + void *p = aligned_malloc(sizeof(U)); + return p != nullptr ? new(p) U : nullptr; + } + + template + static inline U *create(A1 &&a1) { + void *p = aligned_malloc(sizeof(U)); + return p != nullptr ? new(p) U(std::forward(a1)) : nullptr; + } + + template + static inline void destroy(U *p) { + if (p != nullptr) + p->~U(); + aligned_free(p); + } + + private: + std::atomic producerListTail; + std::atomic producerCount; + + std::atomic initialBlockPoolIndex; + Block *initialBlockPool; + size_t initialBlockPoolSize; + +#ifndef MCDBGQ_USEDEBUGFREELIST + FreeList freeList; +#else + debug::DebugFreeList freeList; +#endif + + std::atomic implicitProducerHash; + std::atomic implicitProducerHashCount; // Number of slots logically used + ImplicitProducerHash initialImplicitProducerHash; + std::array initialImplicitProducerHashEntries; + std::atomic_flag implicitProducerHashResizeInProgress; + + std::atomic nextExplicitConsumerId; + std::atomic globalExplicitConsumerOffset; + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugMutex implicitProdMutex; +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + std::atomic explicitProducers; + std::atomic implicitProducers; +#endif + }; + + + template + ProducerToken::ProducerToken(ConcurrentQueue &queue) + : producer(queue.recycle_or_create_producer(true)) { + if (producer != nullptr) { + producer->token = this; + } + } + + template + ProducerToken::ProducerToken(BlockingConcurrentQueue &queue) + : producer(reinterpret_cast *>(&queue)->recycle_or_create_producer(true)) { + if (producer != nullptr) { + producer->token = this; + } + } + + template + ConsumerToken::ConsumerToken(ConcurrentQueue &queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) { + initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); + } + + template + ConsumerToken::ConsumerToken(BlockingConcurrentQueue &queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) { + initialOffset = reinterpret_cast *>(&queue)->nextExplicitConsumerId.fetch_add(1, + std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); + } + + template + inline void swap(ConcurrentQueue &a, ConcurrentQueue &b) + + MOODYCAMEL_NOEXCEPT { + a. + swap(b); +} + +inline void swap(ProducerToken &a, ProducerToken &b) + +MOODYCAMEL_NOEXCEPT +{ +a. +swap(b); +} + +inline void swap(ConsumerToken &a, ConsumerToken &b) + +MOODYCAMEL_NOEXCEPT +{ +a. +swap(b); +} + +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP &a, + typename ConcurrentQueue::ImplicitProducerKVP &b) + +MOODYCAMEL_NOEXCEPT +{ +a. +swap(b); +} + +} + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +#pragma warning(pop) +#endif + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +#pragma GCC diagnostic pop +#endif \ No newline at end of file diff --git a/third_party/readerwriterqueue.h b/third_party/readerwriterqueue.h new file mode 100644 index 00000000..8b26556c --- /dev/null +++ b/third_party/readerwriterqueue.h @@ -0,0 +1,979 @@ +// ©2013-2020 Cameron Desrochers. +// Distributed under the simplified BSD license (see the license file that +// should have come with this header). + +#pragma once + +#include "atomicops.h" +#include +#include +#include +#include +#include +#include +#include +#include // For malloc/free/abort & size_t +#include +#if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012 +#include +#endif + + +// A lock-free queue for a single-consumer, single-producer architecture. +// The queue is also wait-free in the common path (except if more memory +// needs to be allocated, in which case malloc is called). +// Allocates memory sparingly, and only once if the original maximum size +// estimate is never exceeded. +// Tested on x86/x64 processors, but semantics should be correct for all +// architectures (given the right implementations in atomicops.h), provided +// that aligned integer and pointer accesses are naturally atomic. +// Note that there should only be one consumer thread and producer thread; +// Switching roles of the threads, or using multiple consecutive threads for +// one role, is not safe unless properly synchronized. +// Using the queue exclusively from one thread is fine, though a bit silly. + +#ifndef MOODYCAMEL_CACHE_LINE_SIZE +#define MOODYCAMEL_CACHE_LINE_SIZE 64 +#endif + +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#endif +#endif + +#ifndef MOODYCAMEL_HAS_EMPLACE +#if !defined(_MSC_VER) || _MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013 +#define MOODYCAMEL_HAS_EMPLACE 1 +#endif +#endif + +#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#if defined (__APPLE__) && defined (__MACH__) && __cplusplus >= 201703L +// This is required to find out what deployment target we are using +#include +#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14 +// C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we can't support over-alignment in this case +#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#endif +#endif +#endif + +#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE) +#endif + +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable: 4324) // structure was padded due to __declspec(align()) +#pragma warning(disable: 4820) // padding was added +#pragma warning(disable: 4127) // conditional expression is constant +#endif + +namespace moodycamel { + + template + class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue +{ + // Design: Based on a queue-of-queues. The low-level queues are just + // circular buffers with front and tail indices indicating where the + // next element to dequeue is and where the next element can be enqueued, + // respectively. Each low-level queue is called a "block". Each block + // wastes exactly one element's worth of space to keep the design simple + // (if front == tail then the queue is empty, and can't be full). + // The high-level queue is a circular linked list of blocks; again there + // is a front and tail, but this time they are pointers to the blocks. + // The front block is where the next element to be dequeued is, provided + // the block is not empty. The back block is where elements are to be + // enqueued, provided the block is not full. + // The producer thread owns all the tail indices/pointers. The consumer + // thread owns all the front indices/pointers. Both threads read each + // other's variables, but only the owning thread updates them. E.g. After + // the consumer reads the producer's tail, the tail may change before the + // consumer is done dequeuing an object, but the consumer knows the tail + // will never go backwards, only forwards. + // If there is no room to enqueue an object, an additional block (of + // equal size to the last block) is added. Blocks are never removed. + + public: + typedef T value_type; + + // Constructs a queue that can hold at least `size` elements without further + // allocations. If more than MAX_BLOCK_SIZE elements are requested, + // then several blocks of MAX_BLOCK_SIZE each are reserved (including + // at least one extra buffer block). + AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15) +#ifndef NDEBUG + : enqueuing(false) + ,dequeuing(false) +#endif +{ + assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) && "MAX_BLOCK_SIZE must be a power of 2"); + assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2"); + + Block* firstBlock = nullptr; + + largestBlockSize = ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block + if (largestBlockSize > MAX_BLOCK_SIZE * 2) { + // We need a spare block in case the producer is writing to a different block the consumer is reading from, and + // wants to enqueue the maximum number of elements. We also need a spare element in each block to avoid the ambiguity + // between front == tail meaning "empty" and "full". + // So the effective number of slots that are guaranteed to be usable at any time is the block size - 1 times the + // number of blocks - 1. Solving for size and applying a ceiling to the division gives us (after simplifying): + size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1); + largestBlockSize = MAX_BLOCK_SIZE; + Block* lastBlock = nullptr; + for (size_t i = 0; i != initialBlockCount; ++i) { + auto block = make_block(largestBlockSize); + if (block == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#else + abort(); +#endif +} +if (firstBlock == nullptr) { +firstBlock = block; +} +else { +lastBlock->next = block; +} +lastBlock = block; +block->next = firstBlock; +} +} +else { +firstBlock = make_block(largestBlockSize); +if (firstBlock == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED +throw std::bad_alloc(); +#else +abort(); +#endif +} +firstBlock->next = firstBlock; +} +frontBlock = firstBlock; +tailBlock = firstBlock; + +// Make sure the reader/writer threads will have the initialized memory setup above: +fence(memory_order_sync); +} + +// Note: The queue should not be accessed concurrently while it's +// being moved. It's up to the user to synchronize this. +AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other) +: frontBlock(other.frontBlock.load()), +tailBlock(other.tailBlock.load()), +largestBlockSize(other.largestBlockSize) +#ifndef NDEBUG +,enqueuing(false) +,dequeuing(false) +#endif +{ +other.largestBlockSize = 32; +Block* b = other.make_block(other.largestBlockSize); +if (b == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED +throw std::bad_alloc(); +#else +abort(); +#endif +} +b->next = b; +other.frontBlock = b; +other.tailBlock = b; +} + +// Note: The queue should not be accessed concurrently while it's +// being moved. It's up to the user to synchronize this. +ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN +{ +Block* b = frontBlock.load(); +frontBlock = other.frontBlock.load(); +other.frontBlock = b; +b = tailBlock.load(); +tailBlock = other.tailBlock.load(); +other.tailBlock = b; +std::swap(largestBlockSize, other.largestBlockSize); +return *this; +} + +// Note: The queue should not be accessed concurrently while it's +// being deleted. It's up to the user to synchronize this. +AE_NO_TSAN ~ReaderWriterQueue() +{ + // Make sure we get the latest version of all variables from other CPUs: + fence(memory_order_sync); + + // Destroy any remaining objects in queue and free memory + Block* frontBlock_ = frontBlock; + Block* block = frontBlock_; + do { + Block* nextBlock = block->next; + size_t blockFront = block->front; + size_t blockTail = block->tail; + + for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) { + auto element = reinterpret_cast(block->data + i * sizeof(T)); + element->~T(); + (void)element; + } + + auto rawBlock = block->rawThis; + block->~Block(); + std::free(rawBlock); + block = nextBlock; + } while (block != frontBlock_); +} + + +// Enqueues a copy of element if there is room in the queue. +// Returns true if the element was enqueued, false otherwise. +// Does not allocate memory. +AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN +{ +return inner_enqueue(element); +} + +// Enqueues a moved copy of element if there is room in the queue. +// Returns true if the element was enqueued, false otherwise. +// Does not allocate memory. +AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN +{ +return inner_enqueue(std::forward(element)); +} + +#if MOODYCAMEL_HAS_EMPLACE +// Like try_enqueue() but with emplace semantics (i.e. construct-in-place). +template +AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN +{ +return inner_enqueue(std::forward(args)...); +} +#endif + +// Enqueues a copy of element on the queue. +// Allocates an additional block of memory if needed. +// Only fails (returns false) if memory allocation fails. +AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN +{ +return inner_enqueue(element); +} + +// Enqueues a moved copy of element on the queue. +// Allocates an additional block of memory if needed. +// Only fails (returns false) if memory allocation fails. +AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN +{ +return inner_enqueue(std::forward(element)); +} + +#if MOODYCAMEL_HAS_EMPLACE +// Like enqueue() but with emplace semantics (i.e. construct-in-place). +template +AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN +{ +return inner_enqueue(std::forward(args)...); +} +#endif + +// Attempts to dequeue an element; if the queue is empty, +// returns false instead. If the queue has at least one element, +// moves front to result using operator=, then returns true. +template +bool try_dequeue(U& result) AE_NO_TSAN +{ +#ifndef NDEBUG +ReentrantGuard guard(this->dequeuing); +#endif + +// High-level pseudocode: +// Remember where the tail block is +// If the front block has an element in it, dequeue it +// Else +// If front block was the tail block when we entered the function, return false +// Else advance to next block and dequeue the item there + +// Note that we have to use the value of the tail block from before we check if the front +// block is full or not, in case the front block is empty and then, before we check if the +// tail block is at the front block or not, the producer fills up the front block *and +// moves on*, which would make us skip a filled block. Seems unlikely, but was consistently +// reproducible in practice. +// In order to avoid overhead in the common case, though, we do a double-checked pattern +// where we have the fast path if the front block is not empty, then read the tail block, +// then re-read the front block and check if it's not empty again, then check if the tail +// block has advanced. + +Block* frontBlock_ = frontBlock.load(); +size_t blockTail = frontBlock_->localTail; +size_t blockFront = frontBlock_->front.load(); + +if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { +fence(memory_order_acquire); + +non_empty_front_block: +// Front block not empty, dequeue from here +auto element = reinterpret_cast(frontBlock_->data + blockFront * sizeof(T)); +result = std::move(*element); +element->~T(); + +blockFront = (blockFront + 1) & frontBlock_->sizeMask; + +fence(memory_order_release); +frontBlock_->front = blockFront; +} +else if (frontBlock_ != tailBlock.load()) { +fence(memory_order_acquire); + +frontBlock_ = frontBlock.load(); +blockTail = frontBlock_->localTail = frontBlock_->tail.load(); +blockFront = frontBlock_->front.load(); +fence(memory_order_acquire); + +if (blockFront != blockTail) { +// Oh look, the front block isn't empty after all +goto non_empty_front_block; +} + +// Front block is empty but there's another block ahead, advance to it +Block* nextBlock = frontBlock_->next; +// Don't need an acquire fence here since next can only ever be set on the tailBlock, +// and we're not the tailBlock, and we did an acquire earlier after reading tailBlock which +// ensures next is up-to-date on this CPU in case we recently were at tailBlock. + +size_t nextBlockFront = nextBlock->front.load(); +size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); +fence(memory_order_acquire); + +// Since the tailBlock is only ever advanced after being written to, +// we know there's for sure an element to dequeue on it +assert(nextBlockFront != nextBlockTail); +AE_UNUSED(nextBlockTail); + +// We're done with this block, let the producer use it if it needs +fence(memory_order_release); // Expose possibly pending changes to frontBlock->front from last dequeue +frontBlock = frontBlock_ = nextBlock; + +compiler_fence(memory_order_release); // Not strictly needed + +auto element = reinterpret_cast(frontBlock_->data + nextBlockFront * sizeof(T)); + +result = std::move(*element); +element->~T(); + +nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; + +fence(memory_order_release); +frontBlock_->front = nextBlockFront; +} +else { +// No elements in current block and no other block to advance to +return false; +} + +return true; +} + + +// Returns a pointer to the front element in the queue (the one that +// would be removed next by a call to `try_dequeue` or `pop`). If the +// queue appears empty at the time the method is called, nullptr is +// returned instead. +// Must be called only from the consumer thread. +T* peek() const AE_NO_TSAN +{ +#ifndef NDEBUG +ReentrantGuard guard(this->dequeuing); +#endif +// See try_dequeue() for reasoning + +Block* frontBlock_ = frontBlock.load(); +size_t blockTail = frontBlock_->localTail; +size_t blockFront = frontBlock_->front.load(); + +if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { +fence(memory_order_acquire); +non_empty_front_block: +return reinterpret_cast(frontBlock_->data + blockFront * sizeof(T)); +} +else if (frontBlock_ != tailBlock.load()) { +fence(memory_order_acquire); +frontBlock_ = frontBlock.load(); +blockTail = frontBlock_->localTail = frontBlock_->tail.load(); +blockFront = frontBlock_->front.load(); +fence(memory_order_acquire); + +if (blockFront != blockTail) { +goto non_empty_front_block; +} + +Block* nextBlock = frontBlock_->next; + +size_t nextBlockFront = nextBlock->front.load(); +fence(memory_order_acquire); + +assert(nextBlockFront != nextBlock->tail.load()); +return reinterpret_cast(nextBlock->data + nextBlockFront * sizeof(T)); +} + +return nullptr; +} + +// Removes the front element from the queue, if any, without returning it. +// Returns true on success, or false if the queue appeared empty at the time +// `pop` was called. +bool pop() AE_NO_TSAN +{ +#ifndef NDEBUG +ReentrantGuard guard(this->dequeuing); +#endif +// See try_dequeue() for reasoning + +Block* frontBlock_ = frontBlock.load(); +size_t blockTail = frontBlock_->localTail; +size_t blockFront = frontBlock_->front.load(); + +if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { +fence(memory_order_acquire); + +non_empty_front_block: +auto element = reinterpret_cast(frontBlock_->data + blockFront * sizeof(T)); +element->~T(); + +blockFront = (blockFront + 1) & frontBlock_->sizeMask; + +fence(memory_order_release); +frontBlock_->front = blockFront; +} +else if (frontBlock_ != tailBlock.load()) { +fence(memory_order_acquire); +frontBlock_ = frontBlock.load(); +blockTail = frontBlock_->localTail = frontBlock_->tail.load(); +blockFront = frontBlock_->front.load(); +fence(memory_order_acquire); + +if (blockFront != blockTail) { +goto non_empty_front_block; +} + +// Front block is empty but there's another block ahead, advance to it +Block* nextBlock = frontBlock_->next; + +size_t nextBlockFront = nextBlock->front.load(); +size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); +fence(memory_order_acquire); + +assert(nextBlockFront != nextBlockTail); +AE_UNUSED(nextBlockTail); + +fence(memory_order_release); +frontBlock = frontBlock_ = nextBlock; + +compiler_fence(memory_order_release); + +auto element = reinterpret_cast(frontBlock_->data + nextBlockFront * sizeof(T)); +element->~T(); + +nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; + +fence(memory_order_release); +frontBlock_->front = nextBlockFront; +} +else { +// No elements in current block and no other block to advance to +return false; +} + +return true; +} + +// Returns the approximate number of items currently in the queue. +// Safe to call from both the producer and consumer threads. +inline size_t size_approx() const AE_NO_TSAN +{ +size_t result = 0; +Block* frontBlock_ = frontBlock.load(); +Block* block = frontBlock_; +do { +fence(memory_order_acquire); +size_t blockFront = block->front.load(); +size_t blockTail = block->tail.load(); +result += (blockTail - blockFront) & block->sizeMask; +block = block->next.load(); +} while (block != frontBlock_); +return result; +} + +// Returns the total number of items that could be enqueued without incurring +// an allocation when this queue is empty. +// Safe to call from both the producer and consumer threads. +// +// NOTE: The actual capacity during usage may be different depending on the consumer. +// If the consumer is removing elements concurrently, the producer cannot add to +// the block the consumer is removing from until it's completely empty, except in +// the case where the producer was writing to the same block the consumer was +// reading from the whole time. +inline size_t max_capacity() const { + size_t result = 0; + Block* frontBlock_ = frontBlock.load(); + Block* block = frontBlock_; + do { + fence(memory_order_acquire); + result += block->sizeMask; + block = block->next.load(); + } while (block != frontBlock_); + return result; +} + + +private: +enum AllocationMode { CanAlloc, CannotAlloc }; + +#if MOODYCAMEL_HAS_EMPLACE +template +bool inner_enqueue(Args&&... args) AE_NO_TSAN +#else +template + bool inner_enqueue(U&& element) AE_NO_TSAN +#endif +{ +#ifndef NDEBUG +ReentrantGuard guard(this->enqueuing); +#endif + +// High-level pseudocode (assuming we're allowed to alloc a new block): +// If room in tail block, add to tail +// Else check next block +// If next block is not the head block, enqueue on next block +// Else create a new block and enqueue there +// Advance tail to the block we just enqueued to + +Block* tailBlock_ = tailBlock.load(); +size_t blockFront = tailBlock_->localFront; +size_t blockTail = tailBlock_->tail.load(); + +size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask; +if (nextBlockTail != blockFront || nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) { +fence(memory_order_acquire); +// This block has room for at least one more element +char* location = tailBlock_->data + blockTail * sizeof(T); +#if MOODYCAMEL_HAS_EMPLACE +new (location) T(std::forward(args)...); +#else +new (location) T(std::forward(element)); +#endif + +fence(memory_order_release); +tailBlock_->tail = nextBlockTail; +} +else { +fence(memory_order_acquire); +if (tailBlock_->next.load() != frontBlock) { +// Note that the reason we can't advance to the frontBlock and start adding new entries there +// is because if we did, then dequeue would stay in that block, eventually reading the new values, +// instead of advancing to the next full block (whose values were enqueued first and so should be +// consumed first). + +fence(memory_order_acquire); // Ensure we get latest writes if we got the latest frontBlock + +// tailBlock is full, but there's a free block ahead, use it +Block* tailBlockNext = tailBlock_->next.load(); +size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load(); +nextBlockTail = tailBlockNext->tail.load(); +fence(memory_order_acquire); + +// This block must be empty since it's not the head block and we +// go through the blocks in a circle +assert(nextBlockFront == nextBlockTail); +tailBlockNext->localFront = nextBlockFront; + +char* location = tailBlockNext->data + nextBlockTail * sizeof(T); +#if MOODYCAMEL_HAS_EMPLACE +new (location) T(std::forward(args)...); +#else +new (location) T(std::forward(element)); +#endif + +tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask; + +fence(memory_order_release); +tailBlock = tailBlockNext; +} +else if (canAlloc == CanAlloc) { +// tailBlock is full and there's no free block ahead; create a new block +auto newBlockSize = largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2; +auto newBlock = make_block(newBlockSize); +if (newBlock == nullptr) { +// Could not allocate a block! +return false; +} +largestBlockSize = newBlockSize; + +#if MOODYCAMEL_HAS_EMPLACE +new (newBlock->data) T(std::forward(args)...); +#else +new (newBlock->data) T(std::forward(element)); +#endif +assert(newBlock->front == 0); +newBlock->tail = newBlock->localTail = 1; + +newBlock->next = tailBlock_->next.load(); +tailBlock_->next = newBlock; + +// Might be possible for the dequeue thread to see the new tailBlock->next +// *without* seeing the new tailBlock value, but this is OK since it can't +// advance to the next block until tailBlock is set anyway (because the only +// case where it could try to read the next is if it's already at the tailBlock, +// and it won't advance past tailBlock in any circumstance). + +fence(memory_order_release); +tailBlock = newBlock; +} +else if (canAlloc == CannotAlloc) { +// Would have had to allocate a new block to enqueue, but not allowed +return false; +} +else { +assert(false && "Should be unreachable code"); +return false; +} +} + +return true; +} + + +// Disable copying +ReaderWriterQueue(ReaderWriterQueue const&) { } + +// Disable assignment +ReaderWriterQueue& operator=(ReaderWriterQueue const&) { } + + +AE_FORCEINLINE static size_t ceilToPow2(size_t x) +{ +// From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +--x; +x |= x >> 1; +x |= x >> 2; +x |= x >> 4; +for (size_t i = 1; i < sizeof(size_t); i <<= 1) { +x |= x >> (i << 3); +} +++x; +return x; +} + +template +static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN +{ +const std::size_t alignment = std::alignment_of::value; +return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; +} +private: +#ifndef NDEBUG +struct ReentrantGuard +{ + AE_NO_TSAN ReentrantGuard(weak_atomic& _inSection) + : inSection(_inSection) + { + assert(!inSection && "Concurrent (or re-entrant) enqueue or dequeue operation detected (only one thread at a time may hold the producer or consumer role)"); + inSection = true; + } + + AE_NO_TSAN ~ReentrantGuard() { inSection = false; } + +private: + ReentrantGuard& operator=(ReentrantGuard const&); + +private: + weak_atomic& inSection; +}; +#endif + +struct Block +{ + // Avoid false-sharing by putting highly contended variables on their own cache lines + weak_atomic front; // (Atomic) Elements are read from here + size_t localTail; // An uncontended shadow copy of tail, owned by the consumer + + char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic) - sizeof(size_t)]; + weak_atomic tail; // (Atomic) Elements are enqueued here + size_t localFront; + + char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic) - sizeof(size_t)]; // next isn't very contended, but we don't want it on the same cache line as tail (which is) + weak_atomic next; // (Atomic) + + char* data; // Contents (on heap) are aligned to T's alignment + + const size_t sizeMask; + + + // size must be a power of two (and greater than 0) + AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data) + : front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data), sizeMask(_size - 1), rawThis(_rawThis) + { + } + +private: + // C4512 - Assignment operator could not be generated + Block& operator=(Block const&); + +public: + char* rawThis; +}; + + +static Block* make_block(size_t capacity) AE_NO_TSAN +{ +// Allocate enough memory for the block itself, as well as all the elements it will contain +auto size = sizeof(Block) + std::alignment_of::value - 1; +size += sizeof(T) * capacity + std::alignment_of::value - 1; +auto newBlockRaw = static_cast(std::malloc(size)); +if (newBlockRaw == nullptr) { +return nullptr; +} + +auto newBlockAligned = align_for(newBlockRaw); +auto newBlockData = align_for(newBlockAligned + sizeof(Block)); +return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData); +} + +private: +weak_atomic frontBlock; // (Atomic) Elements are dequeued from this block + +char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic)]; +weak_atomic tailBlock; // (Atomic) Elements are enqueued to this block + +size_t largestBlockSize; + +#ifndef NDEBUG +weak_atomic enqueuing; +mutable weak_atomic dequeuing; +#endif +}; + +// Like ReaderWriterQueue, but also providees blocking operations +template +class BlockingReaderWriterQueue +{ +private: + typedef ::moodycamel::ReaderWriterQueue ReaderWriterQueue; + +public: + explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN + : inner(size), sema(new spsc_sema::LightweightSemaphore()) + { } + + BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN + : inner(std::move(other.inner)), sema(std::move(other.sema)) + { } + + BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN + { + std::swap(sema, other.sema); + std::swap(inner, other.inner); + return *this; + } + + + // Enqueues a copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN + { + if (inner.try_enqueue(element)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a moved copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN + { + if (inner.try_enqueue(std::forward(element))) { + sema->signal(); + return true; + } + return false; + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). + template + AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN + { + if (inner.try_emplace(std::forward(args)...)) { + sema->signal(); + return true; + } + return false; + } +#endif + + + // Enqueues a copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN + { + if (inner.enqueue(element)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a moved copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN + { + if (inner.enqueue(std::forward(element))) { + sema->signal(); + return true; + } + return false; + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like enqueue() but with emplace semantics (i.e. construct-in-place). + template + AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN + { + if (inner.emplace(std::forward(args)...)) { + sema->signal(); + return true; + } + return false; + } +#endif + + + // Attempts to dequeue an element; if the queue is empty, + // returns false instead. If the queue has at least one element, + // moves front to result using operator=, then returns true. + template + bool try_dequeue(U& result) AE_NO_TSAN + { + if (sema->tryWait()) { + bool success = inner.try_dequeue(result); + assert(success); + AE_UNUSED(success); + return true; + } + return false; + } + + + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available, then dequeues it. + template + void wait_dequeue(U& result) AE_NO_TSAN + { + while (!sema->wait()); + bool success = inner.try_dequeue(result); + AE_UNUSED(result); + assert(success); + AE_UNUSED(success); + } + + + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available up to the specified timeout, + // then dequeues it and returns true, or returns false if the timeout + // expires before an element can be dequeued. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + template + bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN + { + if (!sema->wait(timeout_usecs)) { + return false; + } + bool success = inner.try_dequeue(result); + AE_UNUSED(result); + assert(success); + AE_UNUSED(success); + return true; + } + + +#if __cplusplus > 199711L || _MSC_VER >= 1700 + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available up to the specified timeout, + // then dequeues it and returns true, or returns false if the timeout + // expires before an element can be dequeued. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + template + inline bool wait_dequeue_timed(U& result, std::chrono::duration const& timeout) AE_NO_TSAN + { + return wait_dequeue_timed(result, std::chrono::duration_cast(timeout).count()); + } +#endif + + + // Returns a pointer to the front element in the queue (the one that + // would be removed next by a call to `try_dequeue` or `pop`). If the + // queue appears empty at the time the method is called, nullptr is + // returned instead. + // Must be called only from the consumer thread. + AE_FORCEINLINE T* peek() const AE_NO_TSAN + { + return inner.peek(); + } + + // Removes the front element from the queue, if any, without returning it. + // Returns true on success, or false if the queue appeared empty at the time + // `pop` was called. + AE_FORCEINLINE bool pop() AE_NO_TSAN + { + if (sema->tryWait()) { + bool result = inner.pop(); + assert(result); + AE_UNUSED(result); + return true; + } + return false; + } + + // Returns the approximate number of items currently in the queue. + // Safe to call from both the producer and consumer threads. + AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN + { + return sema->availableApprox(); + } + + // Returns the total number of items that could be enqueued without incurring + // an allocation when this queue is empty. + // Safe to call from both the producer and consumer threads. + // + // NOTE: The actual capacity during usage may be different depending on the consumer. + // If the consumer is removing elements concurrently, the producer cannot add to + // the block the consumer is removing from until it's completely empty, except in + // the case where the producer was writing to the same block the consumer was + // reading from the whole time. + AE_FORCEINLINE size_t max_capacity() const { + return inner.max_capacity(); + } + +private: + // Disable copying & assignment + BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) { } + BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) { } + +private: + ReaderWriterQueue inner; + std::unique_ptr sema; +}; + +} // end namespace moodycamel + +#ifdef AE_VCPP +#pragma warning(pop) +#endif \ No newline at end of file diff --git a/zmq_src/Agent.cpp b/zmq_src/Agent.cpp new file mode 100644 index 00000000..4b27aa01 --- /dev/null +++ b/zmq_src/Agent.cpp @@ -0,0 +1,51 @@ +/* + Copyright (C) 2021 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 . + + @file Agent.cpp + @author Stan Kladko + @date 2021 +*/ + +#include "common.h" +#include "Agent.h" + +Agent::Agent() : startedWorkers(false) {}; + +void Agent::waitOnGlobalStartBarrier() { + unique_lock lock(startMutex); + while (!startedWorkers) { + // wait until notified to start running + startCond.wait(lock); + } +} + +Agent::~Agent() { +} + + +void Agent::releaseWorkers() { + + if (startedWorkers.exchange(true)) { + // already started + return; + } + + lock_guard lock(startMutex); + + startCond.notify_all(); +} diff --git a/zmq_src/Agent.h b/zmq_src/Agent.h new file mode 100644 index 00000000..803c2d5a --- /dev/null +++ b/zmq_src/Agent.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2021 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 . + + @file Agent.h + @author Stan Kladko + @date 2021 +*/ + +#pragma once + +class Agent { + +protected: + + atomic_bool startedWorkers; + + mutex messageMutex; + condition_variable messageCond; + + condition_variable startCond; + mutex startMutex; + + recursive_mutex m; + + +public: + + Agent(); + + virtual ~Agent(); + + void releaseWorkers(); + + void waitOnGlobalStartBarrier(); + + recursive_mutex& getMainMutex() { return m; } +}; diff --git a/zmq_src/ReqMessage.cpp b/zmq_src/ReqMessage.cpp new file mode 100644 index 00000000..413d01eb --- /dev/null +++ b/zmq_src/ReqMessage.cpp @@ -0,0 +1,275 @@ +/* + Copyright (C) 2018- 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 . + + @file ReqMessage.cpp + @author Oleh Nikolaiev + @date 2021 +*/ + + +#include "SGXWalletServer.hpp" + +#include "ReqMessage.h" + +#include "third_party/spdlog/spdlog.h" + +Json::Value ECDSASignReqMessage::process() { + auto base = getInt64Rapid("base"); + auto keyName = getStringRapid("keyName"); + auto hash = getStringRapid("messageHash"); + if (checkKeyOwnership) { + if (!isKeyRegistered(keyName)) { + addKeyByOwner(keyName, getStringRapid("cert")); + } else { + if (!isKeyByOwner(keyName, getStringRapid("cert"))) { + spdlog::error("Cert {} try to access key {} which does not belong to it", getStringRapid("cert"), keyName); + throw std::invalid_argument("Only owner of the key can access it"); + } + } + } + auto result = SGXWalletServer::ecdsaSignMessageHashImpl(base, keyName, hash); + result["type"] = ZMQMessage::ECDSA_SIGN_RSP; + return result; +} + +Json::Value BLSSignReqMessage::process() { + auto keyName = getStringRapid("keyShareName"); + auto hash = getStringRapid("messageHash"); + auto t = getInt64Rapid("t"); + auto n = getInt64Rapid("n"); + if (checkKeyOwnership) { + if (!isKeyRegistered(keyName)) { + addKeyByOwner(keyName, getStringRapid("cert")); + } else { + if (!isKeyByOwner(keyName, getStringRapid("cert"))) { + spdlog::error("Cert {} try to access key {} which does not belong to it", getStringRapid("cert"), keyName); + throw std::invalid_argument("Only owner of the key can access it"); + } + } + } + auto result = SGXWalletServer::blsSignMessageHashImpl(keyName, hash, t, n); + result["type"] = ZMQMessage::BLS_SIGN_RSP; + return result; +} + +Json::Value importBLSReqMessage::process() { + auto keyName = getStringRapid("keyShareName"); + auto keyShare = getStringRapid("keyShare"); + auto result = SGXWalletServer::importBLSKeyShareImpl(keyShare, keyName); + if (checkKeyOwnership && result["status"] == 0) { + spdlog::info("Cert {} creates key {}", getStringRapid("cert"), keyName); + auto cert = getStringRapid("cert"); + addKeyByOwner(keyName, cert); + } + result["type"] = ZMQMessage::IMPORT_BLS_RSP; + return result; +} + +Json::Value importECDSAReqMessage::process() { + auto keyName = getStringRapid("keyName"); + auto key = getStringRapid("key"); + auto result = SGXWalletServer::importECDSAKeyImpl(key, keyName); + if (checkKeyOwnership && result["status"] == 0) { + auto cert = getStringRapid("cert"); + spdlog::info("Cert {} creates key {}", cert, keyName); + addKeyByOwner(keyName, cert); + } + result["type"] = ZMQMessage::IMPORT_ECDSA_RSP; + return result; +} + +Json::Value generateECDSAReqMessage::process() { + auto result = SGXWalletServer::generateECDSAKeyImpl(); + string keyName = result["keyName"].asString(); + if (checkKeyOwnership && result["status"] == 0) { + auto cert = getStringRapid("cert"); + spdlog::info("Cert {} creates key {}", cert, keyName); + addKeyByOwner(keyName, cert); + } + result["type"] = ZMQMessage::GENERATE_ECDSA_RSP; + return result; +} + +Json::Value getPublicECDSAReqMessage::process() { + auto keyName = getStringRapid("keyName"); + if (checkKeyOwnership && !isKeyByOwner(keyName, getStringRapid("cert"))) { + spdlog::error("Cert {} try to access key {} which does not belong to it", getStringRapid("cert"), keyName); + throw std::invalid_argument("Only owner of the key can access it"); + } + auto result = SGXWalletServer::getPublicECDSAKeyImpl(keyName); + result["type"] = ZMQMessage::GET_PUBLIC_ECDSA_RSP; + return result; +} + +Json::Value generateDKGPolyReqMessage::process() { + auto polyName = getStringRapid("polyName"); + auto t = getInt64Rapid("t"); + auto result = SGXWalletServer::generateDKGPolyImpl(polyName, t); + if (checkKeyOwnership && result["status"] == 0) { + auto cert = getStringRapid("cert"); + spdlog::info("Cert {} creates key {}", cert, polyName); + addKeyByOwner(polyName, cert); + } + result["type"] = ZMQMessage::GENERATE_DKG_POLY_RSP; + return result; +} + +Json::Value getVerificationVectorReqMessage::process() { + auto polyName = getStringRapid("polyName"); + if (checkKeyOwnership && !isKeyByOwner(polyName, getStringRapid("cert"))) { + spdlog::error("Cert {} try to access key {} which does not belong to it", getStringRapid("cert"), polyName); + throw std::invalid_argument("Only owner of the key can access it"); + } + auto t = getInt64Rapid("t"); + auto result = SGXWalletServer::getVerificationVectorImpl(polyName, t); + result["type"] = ZMQMessage::GET_VV_RSP; + return result; +} + +Json::Value getSecretShareReqMessage::process() { + auto polyName = getStringRapid("polyName"); + auto t = getInt64Rapid("t"); + auto n = getInt64Rapid("n"); + auto pubKeys = getJsonValueRapid("publicKeys"); + if (checkKeyOwnership && !isKeyByOwner(polyName, getStringRapid("cert"))) { + spdlog::error("Cert {} try to access key {} which does not belong to it", getStringRapid("cert"), polyName); + throw std::invalid_argument("Only owner of the key can access it"); + } + auto result = SGXWalletServer::getSecretShareV2Impl(polyName, pubKeys, t, n); + result["type"] = ZMQMessage::GET_SECRET_SHARE_RSP; + return result; +} + +Json::Value dkgVerificationReqMessage::process() { + auto ethKeyName = getStringRapid("ethKeyName"); + auto t = getInt64Rapid("t"); + auto n = getInt64Rapid("n"); + auto idx = getInt64Rapid("index"); + auto pubShares = getStringRapid("publicShares"); + auto secretShare = getStringRapid("secretShare"); + if (checkKeyOwnership && !isKeyByOwner(ethKeyName, getStringRapid("cert"))) { + spdlog::error("Cert {} try to access key {} which does not belong to it", getStringRapid("cert"), ethKeyName); + throw std::invalid_argument("Only owner of the key can access it"); + } + auto result = SGXWalletServer::dkgVerificationV2Impl(pubShares, ethKeyName, secretShare, t, n, idx); + result["type"] = ZMQMessage::DKG_VERIFY_RSP; + return result; +} + +Json::Value createBLSPrivateKeyReqMessage::process() { + auto blsKeyName = getStringRapid("blsKeyName"); + auto ethKeyName = getStringRapid("ethKeyName"); + auto polyName = getStringRapid("polyName"); + auto secretShare = getStringRapid("secretShare"); + auto t = getInt64Rapid("t"); + auto n = getInt64Rapid("n"); + if (checkKeyOwnership && (!isKeyByOwner(ethKeyName, getStringRapid("cert")) || !isKeyByOwner(polyName, getStringRapid("cert")))) { + spdlog::error("Cert {} try to access keys {} {} which do not belong to it", getStringRapid("cert"), ethKeyName ,polyName); + throw std::invalid_argument("Only owner of the key can access it"); + } + auto result = SGXWalletServer::createBLSPrivateKeyV2Impl(blsKeyName, ethKeyName, polyName, secretShare, t, n); + if (checkKeyOwnership && result["status"] == 0) { + auto cert = getStringRapid("cert"); + spdlog::info("Cert {} creates key {}", cert, blsKeyName); + addKeyByOwner(blsKeyName, cert); + } + result["type"] = ZMQMessage::CREATE_BLS_PRIVATE_RSP; + return result; +} + +Json::Value getBLSPublicReqMessage::process() { + auto blsKeyName = getStringRapid("blsKeyName"); + if (checkKeyOwnership && !isKeyByOwner(blsKeyName, getStringRapid("cert"))) { + spdlog::error("Cert {} try to access key {} which does not belong to it", getStringRapid("cert"), blsKeyName); + throw std::invalid_argument("Only owner of the key can access it"); + } + auto result = SGXWalletServer::getBLSPublicKeyShareImpl(blsKeyName); + result["type"] = ZMQMessage::GET_BLS_PUBLIC_RSP; + return result; +} + +Json::Value getAllBLSPublicKeysReqMessage::process() { + auto t = getInt64Rapid("t"); + auto n = getInt64Rapid("n"); + auto pubShares = getJsonValueRapid("publicShares"); + auto result = SGXWalletServer::calculateAllBLSPublicKeysImpl(pubShares, t, n); + result["type"] = ZMQMessage::GET_ALL_BLS_PUBLIC_RSP; + return result; +} + +Json::Value complaintResponseReqMessage::process() { + auto polyName = getStringRapid("polyName"); + auto t = getInt64Rapid("t"); + auto n = getInt64Rapid("n"); + auto idx = getInt64Rapid("ind"); + if (checkKeyOwnership && !isKeyByOwner(polyName, getStringRapid("cert"))) { + spdlog::error("Cert {} try to access key {} which does not belong to it", getStringRapid("cert"), polyName); + throw std::invalid_argument("Only owner of the key can access it"); + } + auto result = SGXWalletServer::complaintResponseImpl(polyName, t, n, idx); + result["type"] = ZMQMessage::COMPLAINT_RESPONSE_RSP; + return result; +} + +Json::Value multG2ReqMessage::process() { + auto x = getStringRapid("x"); + auto result = SGXWalletServer::multG2Impl(x); + result["type"] = ZMQMessage::MULT_G2_RSP; + return result; +} + +Json::Value isPolyExistsReqMessage::process() { + auto polyName = getStringRapid("polyName"); + auto result = SGXWalletServer::isPolyExistsImpl(polyName); + result["type"] = ZMQMessage::IS_POLY_EXISTS_RSP; + return result; +} + +Json::Value getServerStatusReqMessage::process() { + auto result = SGXWalletServer::getServerStatusImpl(); + result["type"] = ZMQMessage::GET_SERVER_STATUS_RSP; + return result; +} + +Json::Value getServerVersionReqMessage::process() { + auto result = SGXWalletServer::getServerVersionImpl(); + result["type"] = ZMQMessage::GET_SERVER_VERSION_RSP; + return result; +} + +Json::Value deleteBLSKeyReqMessage::process() { + auto blsKeyName = getStringRapid("blsKeyName"); + if (checkKeyOwnership && !isKeyByOwner(blsKeyName, getStringRapid("cert"))) { + spdlog::error("Cert {} try to access key {} which does not belong to it", getStringRapid("cert"), blsKeyName); + throw std::invalid_argument("Only owner of the key can access it"); + } + auto result = SGXWalletServer::deleteBlsKeyImpl(blsKeyName); + result["type"] = ZMQMessage::DELETE_BLS_KEY_RSP; + return result; +} + +Json::Value GetDecryptionShareReqMessage::process() { + auto blsKeyName = getStringRapid("blsKeyName"); + auto publicDecryptionValue = getStringRapid("publicDecryptionValue"); + if (checkKeyOwnership && !isKeyByOwner(blsKeyName, getStringRapid("cert"))) { + throw std::invalid_argument("Only owner of the key can access it"); + } + auto result = SGXWalletServer::getDecryptionShareImpl(blsKeyName, publicDecryptionValue); + result["type"] = ZMQMessage::GET_DECRYPTION_SHARE_RSP; + return result; +} diff --git a/zmq_src/ReqMessage.h b/zmq_src/ReqMessage.h new file mode 100644 index 00000000..cb1815fc --- /dev/null +++ b/zmq_src/ReqMessage.h @@ -0,0 +1,188 @@ +/* + Copyright (C) 2018- 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 . + + @file ReqMessage.h + @author Oleh Nikolaiev + @date 2021 +*/ + +#ifndef SGXWALLET_REQMESSAGE_H +#define SGXWALLET_REQMESSAGE_H + +#include "ZMQMessage.h" + +class ECDSASignReqMessage : public ZMQMessage { +public: + + ECDSASignReqMessage(shared_ptr &_d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class BLSSignReqMessage : public ZMQMessage { +public: + BLSSignReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class importBLSReqMessage : public ZMQMessage { +public: + importBLSReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class importECDSAReqMessage : public ZMQMessage { +public: + importECDSAReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class generateECDSAReqMessage : public ZMQMessage { +public: + generateECDSAReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class getPublicECDSAReqMessage : public ZMQMessage { +public: + getPublicECDSAReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class generateDKGPolyReqMessage : public ZMQMessage { +public: + generateDKGPolyReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class getVerificationVectorReqMessage : public ZMQMessage { +public: + getVerificationVectorReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class getSecretShareReqMessage : public ZMQMessage { +public: + getSecretShareReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class dkgVerificationReqMessage : public ZMQMessage { +public: + dkgVerificationReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class createBLSPrivateKeyReqMessage : public ZMQMessage { +public: + createBLSPrivateKeyReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class getBLSPublicReqMessage : public ZMQMessage { +public: + getBLSPublicReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class getAllBLSPublicKeysReqMessage : public ZMQMessage { +public: + getAllBLSPublicKeysReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class complaintResponseReqMessage : public ZMQMessage { +public: + complaintResponseReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class multG2ReqMessage : public ZMQMessage { +public: + multG2ReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class isPolyExistsReqMessage : public ZMQMessage { +public: + isPolyExistsReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class getServerStatusReqMessage : public ZMQMessage { +public: + getServerStatusReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class getServerVersionReqMessage : public ZMQMessage { +public: + getServerVersionReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class deleteBLSKeyReqMessage : public ZMQMessage { +public: + deleteBLSKeyReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + +class GetDecryptionShareReqMessage : public ZMQMessage { +public: + GetDecryptionShareReqMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + +#endif //SGXWALLET_REQMESSAGE_H diff --git a/zmq_src/RspMessage.cpp b/zmq_src/RspMessage.cpp new file mode 100644 index 00000000..61840678 --- /dev/null +++ b/zmq_src/RspMessage.cpp @@ -0,0 +1,116 @@ +/* + Copyright (C) 2018- 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 . + + @file RspMessage.cpp + @author Oleh Nikolaiev + @date 2021 +*/ + +#include "SGXWalletServer.hpp" + +#include "RspMessage.h" + +Json::Value ECDSASignRspMessage::process() { + assert(false); +} + +string ECDSASignRspMessage::getSignature() { + string r = getStringRapid("signature_r"); + string v = getStringRapid("signature_v"); + string s = getStringRapid("signature_s"); + + auto ret = v + ":" + r.substr( 2 ) + ":" + s.substr( 2 ); + + return ret; +} + +Json::Value BLSSignRspMessage::process() { + assert(false); +} + +Json::Value importBLSRspMessage::process() { + assert(false); +} + +Json::Value importECDSARspMessage::process() { + assert(false); +} + +Json::Value generateECDSARspMessage::process() { + assert(false); +} + +Json::Value getPublicECDSARspMessage::process() { + assert(false); +} + +Json::Value generateDKGPolyRspMessage::process() { + assert(false); +} + +Json::Value getVerificationVectorRspMessage::process() { + assert(false); +} + +Json::Value getSecretShareRspMessage::process() { + assert(false); +} + +Json::Value dkgVerificationRspMessage::process() { + assert(false); +} + +Json::Value createBLSPrivateKeyRspMessage::process() { + assert(false); +} + +Json::Value getBLSPublicRspMessage::process() { + assert(false); +} + +Json::Value getAllBLSPublicKeysRspMessage::process() { + assert(false); +} + +Json::Value complaintResponseRspMessage::process() { + assert(false); +} + +Json::Value multG2RspMessage::process() { + assert(false); +} + +Json::Value isPolyExistsRspMessage::process() { + assert(false); +} + +Json::Value getServerStatusRspMessage::process() { + assert(false); +} + +Json::Value getServerVersionRspMessage::process() { + assert(false); +} + +Json::Value deleteBLSKeyRspMessage::process() { + assert(false); +} + +Json::Value GetDecryptionShareRspMessage::process() { + assert(false); +} diff --git a/zmq_src/RspMessage.h b/zmq_src/RspMessage.h new file mode 100644 index 00000000..cb7e045e --- /dev/null +++ b/zmq_src/RspMessage.h @@ -0,0 +1,262 @@ +/* + Copyright (C) 2018- 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 . + + @file RspMessage.h + @author Oleh Nikolaiev + @date 2021 +*/ + +#ifndef SGXWALLET_RSPMESSAGE_H +#define SGXWALLET_RSPMESSAGE_H + +#include "ZMQMessage.h" + +class ECDSASignRspMessage : public ZMQMessage { +public: + ECDSASignRspMessage(shared_ptr &_d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + string getSignature(); +}; + + +class BLSSignRspMessage : public ZMQMessage { +public: + BLSSignRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + string getSigShare() { + return getStringRapid("signatureShare"); + } +}; + + +class importBLSRspMessage : public ZMQMessage { +public: + importBLSRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class importECDSARspMessage : public ZMQMessage { +public: + importECDSARspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + string getECDSAPublicKey() { + return getStringRapid("publicKey"); + } +}; + + +class generateECDSARspMessage : public ZMQMessage { +public: + generateECDSARspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + string getECDSAPublicKey() { + return getStringRapid("publicKey"); + } + + string getKeyName() { + return getStringRapid("keyName"); + } +}; + + +class getPublicECDSARspMessage : public ZMQMessage { +public: + getPublicECDSARspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + string getECDSAPublicKey() { + return getStringRapid("publicKey"); + } +}; + + +class generateDKGPolyRspMessage : public ZMQMessage { +public: + generateDKGPolyRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class getVerificationVectorRspMessage : public ZMQMessage { +public: + getVerificationVectorRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + Json::Value getVerificationVector() { + return getJsonValueRapid("verificationVector"); + } +}; + + +class getSecretShareRspMessage : public ZMQMessage { +public: + getSecretShareRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + string getSecretShare() { + return getStringRapid("secretShare"); + } +}; + + +class dkgVerificationRspMessage : public ZMQMessage { +public: + dkgVerificationRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + bool isCorrect() { + return getBoolRapid("result"); + } +}; + + +class createBLSPrivateKeyRspMessage : public ZMQMessage { +public: + createBLSPrivateKeyRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class getBLSPublicRspMessage : public ZMQMessage { +public: + getBLSPublicRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + Json::Value getBLSPublicKey() { + return getJsonValueRapid("blsPublicKeyShare"); + } +}; + + +class getAllBLSPublicKeysRspMessage : public ZMQMessage { +public: + getAllBLSPublicKeysRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + Json::Value getPublicKeys() { + return getJsonValueRapid("publicKeys"); + } +}; + + +class complaintResponseRspMessage : public ZMQMessage { +public: + complaintResponseRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + string getDHKey() { + return getStringRapid("dhKey"); + } + + string getShare() { + return getStringRapid("share*G2"); + } + + Json::Value getVerificationVectorMult() { + return getJsonValueRapid("verificationVectorMult"); + } +}; + + +class multG2RspMessage : public ZMQMessage { +public: + multG2RspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + Json::Value getResult() { + return getJsonValueRapid("x*G2"); + } +}; + + +class isPolyExistsRspMessage : public ZMQMessage { +public: + isPolyExistsRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + bool isExists() { + return getBoolRapid("IsExist"); + } +}; + + +class getServerStatusRspMessage : public ZMQMessage { +public: + getServerStatusRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); +}; + + +class getServerVersionRspMessage : public ZMQMessage { +public: + getServerVersionRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + string getVersion() { + return getStringRapid("version"); + } +}; + + +class deleteBLSKeyRspMessage : public ZMQMessage { +public: + deleteBLSKeyRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + bool isSuccessful() { + return getBoolRapid("deleted"); + } +}; + + +class GetDecryptionShareRspMessage : public ZMQMessage { +public: + GetDecryptionShareRspMessage(shared_ptr& _d) : ZMQMessage(_d) {}; + + virtual Json::Value process(); + + Json::Value getShare() { + return getJsonValueRapid("decryptionShare"); + } +}; + +#endif //SGXWALLET_RSPMESSAGE_H diff --git a/zmq_src/WorkerThreadPool.cpp b/zmq_src/WorkerThreadPool.cpp new file mode 100644 index 00000000..c1bf2c13 --- /dev/null +++ b/zmq_src/WorkerThreadPool.cpp @@ -0,0 +1,87 @@ +/* + Copyright (C) 2021 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 . + + @file WorkerThreadPool.cpp + @author Stan Kladko + @date 2021 +*/ + +#include "document.h" +#include "stringbuffer.h" +#include "writer.h" + + + +#include "common.h" +#include "sgxwallet_common.h" +#include "third_party/spdlog/spdlog.h" +#include "ZMQServer.h" +#include "WorkerThreadPool.h" + + +WorkerThreadPool::WorkerThreadPool(uint64_t _numThreads, ZMQServer *_agent) : joined(false) { + CHECK_STATE(_numThreads > 0); + CHECK_STATE(_agent); + + spdlog::info("Creating thread pool. Threads count:" + to_string(_numThreads)); + + this->agent = _agent; + this->numThreads = _numThreads;; + + + for (uint64_t i = 0; i < (uint64_t) numThreads; i++) { + createThread(i); + } + + spdlog::info("Created thread pool"); + +} + + +void WorkerThreadPool::joinAll() { + + spdlog::info("Joining worker threads ..."); + + if (joined.exchange(true)) + return; + + for (auto &&thread : threadpool) { + if (thread->joinable()) + thread->join(); + CHECK_STATE(!thread->joinable()); + } + + spdlog::info("Joined worker threads."); +} + +bool WorkerThreadPool::isJoined() const { + return joined; +} + +WorkerThreadPool::~WorkerThreadPool(){ +} + +void WorkerThreadPool::createThread(uint64_t _threadNumber) { + + spdlog::info("Starting ZMQ worker thread " + to_string(_threadNumber) ); + + this->threadpool.push_back( + make_shared< thread >( ZMQServer::workerThreadMessageProcessLoop, agent, _threadNumber ) ); + + spdlog::info("Started ZMQ worker thread " + to_string(_threadNumber) ); +} diff --git a/zmq_src/WorkerThreadPool.h b/zmq_src/WorkerThreadPool.h new file mode 100644 index 00000000..d23ffdc2 --- /dev/null +++ b/zmq_src/WorkerThreadPool.h @@ -0,0 +1,53 @@ +/* + 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 . + + @file WorkerThreadPool.h + @author Stan Kladko + @date 2018 +*/ + +#pragma once + +class Agent; +class ZMQServer; + +class WorkerThreadPool { + + void createThread( uint64_t threadNumber ); + + recursive_mutex m; + +protected: + + atomic_bool joined; + vector> threadpool; + + uint64_t numThreads = 0; + ZMQServer* agent = nullptr; + +public: + + WorkerThreadPool(uint64_t _numThreads, ZMQServer *_agent); + + virtual ~WorkerThreadPool(); + + void joinAll(); + + bool isJoined() const; + +}; diff --git a/ZMQClient.cpp b/zmq_src/ZMQClient.cpp similarity index 53% rename from ZMQClient.cpp rename to zmq_src/ZMQClient.cpp index 6e05f00e..ae5353ff 100644 --- a/ZMQClient.cpp +++ b/zmq_src/ZMQClient.cpp @@ -1,20 +1,20 @@ /* Copyright (C) 2018-2019 SKALE Labs - This file is part of skale-consensus. + This file is part of sgxwallet. - skale-consensus is free software: you can redistribute it and/or modify + 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. - skale-consensus is distributed in the hope that it will be useful, + 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 skale-consensus. If not, see . + along with sgxwallet. If not, see . @file ZMQClient.cpp @author Stan Kladko @@ -32,11 +32,9 @@ #include "sgxwallet_common.h" #include "common.h" -#include "BLSCrypto.h" -#include "BLSSignReqMessage.h" -#include "BLSSignRspMessage.h" -#include "ECDSASignReqMessage.h" -#include "ECDSASignRspMessage.h" +#include "CryptoTools.h" +#include "ReqMessage.h" +#include "RspMessage.h" #include "ZMQClient.h" @@ -57,12 +55,10 @@ shared_ptr ZMQClient::doRequestReply(Json::Value &_req) { string reqStr = fastWriter.write(_req); - reqStr = reqStr.substr(0, reqStr.size() - 1); CHECK_STATE(reqStr.front() == '{'); CHECK_STATE(reqStr.at(reqStr.size() - 1) == '}'); - auto resultStr = doZmqRequestReply(reqStr); try { @@ -71,8 +67,7 @@ shared_ptr ZMQClient::doRequestReply(Json::Value &_req) { CHECK_STATE(resultStr.front() == '{') CHECK_STATE(resultStr.back() == '}') - - return ZMQMessage::parse(resultStr.c_str(), resultStr.size(), false, false); + return ZMQMessage::parse(resultStr.c_str(), resultStr.size(), false, false, false); } catch (std::exception &e) { spdlog::error(string("Error in doRequestReply:") + e.what()); throw; @@ -80,11 +75,8 @@ shared_ptr ZMQClient::doRequestReply(Json::Value &_req) { spdlog::error("Error in doRequestReply"); throw; } - - } - string ZMQClient::doZmqRequestReply(string &_req) { stringstream request; @@ -127,15 +119,12 @@ string ZMQClient::doZmqRequestReply(string &_req) { } } - string ZMQClient::readFileIntoString(const string &_fileName) { ifstream t(_fileName); string str((istreambuf_iterator(t)), istreambuf_iterator()); return str; } - - void ZMQClient::verifySig(EVP_PKEY* _pubkey, const string& _str, const string& _sig) { CHECK_STATE(_pubkey); @@ -164,11 +153,6 @@ void ZMQClient::verifySig(EVP_PKEY* _pubkey, const string& _str, const string& _ CHECK_STATE(EVP_DigestVerifyUpdate(mdctx, msgToSign.c_str(), msgToSign.size()) == 1); -/* First call EVP_DigestSignFinal with a NULL sig parameter to obtain the length of the - * signature. Length is returned in slen */ - - - CHECK_STATE2(EVP_DigestVerifyFinal(mdctx, binSig.data(), binLen) == 1, ZMQ_COULD_NOT_VERIFY_SIG); @@ -177,7 +161,6 @@ void ZMQClient::verifySig(EVP_PKEY* _pubkey, const string& _str, const string& _ return; } - string ZMQClient::signString(EVP_PKEY* _pkey, const string& _str) { CHECK_STATE(_pkey); @@ -186,8 +169,6 @@ string ZMQClient::signString(EVP_PKEY* _pkey, const string& _str) { static std::regex r("\\s+"); auto msgToSign = std::regex_replace(_str, r, ""); - - EVP_MD_CTX *mdctx = NULL; int ret = 0; unsigned char *signature = NULL; @@ -196,10 +177,8 @@ string ZMQClient::signString(EVP_PKEY* _pkey, const string& _str) { CHECK_STATE(mdctx = EVP_MD_CTX_create()); - CHECK_STATE((EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, _pkey) == 1)); - CHECK_STATE(EVP_DigestSignUpdate(mdctx, msgToSign.c_str(), msgToSign.size()) == 1); /* First call EVP_DigestSignFinal with a NULL sig parameter to obtain the length of the @@ -278,7 +257,6 @@ ZMQClient::ZMQClient(const string &ip, uint16_t port, bool _sign, const string & certFileName = _certFileName; certKeyName = _certKeyName; - url = "tcp://" + ip + ":" + to_string(port); } @@ -291,13 +269,13 @@ void ZMQClient::reconnect() { clientSockets.erase( pid ); } - uint64_t randNumber; + uint64_t randNumber; CHECK_STATE(getrandom( &randNumber, sizeof(uint64_t), 0 ) == sizeof(uint64_t)); string identity = to_string(135) + ":" + to_string(randNumber); auto clientSocket = make_shared< zmq::socket_t >( ctx, ZMQ_DEALER ); - clientSocket->setsockopt( ZMQ_IDENTITY, identity.c_str(), identity.size() + 1); + clientSocket->setsockopt( ZMQ_IDENTITY, identity.c_str(), identity.size() + 1); // Configure socket to not wait at close time int linger = 0; clientSocket->setsockopt( ZMQ_LINGER, &linger, sizeof( linger ) ); @@ -331,7 +309,204 @@ string ZMQClient::ecdsaSignMessageHash(int base, const std::string &keyName, con return result->getSignature(); } +bool ZMQClient::importBLSKeyShare(const std::string& keyShare, const std::string& keyName) { + Json::Value p; + p["type"] = ZMQMessage::IMPORT_BLS_REQ; + p["keyShareName"] = keyName; + p["keyShare"] = keyShare; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + return result->getStatus() == 0; +} + +string ZMQClient::importECDSAKey(const std::string& keyShare, const std::string& keyName) { + Json::Value p; + p["type"] = ZMQMessage::IMPORT_ECDSA_REQ; + p["keyName"] = keyName; + p["key"] = keyShare; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->getECDSAPublicKey(); +} + +pair ZMQClient::generateECDSAKey() { + Json::Value p; + p["type"] = ZMQMessage::GENERATE_ECDSA_REQ; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return {result->getECDSAPublicKey(), result->getKeyName()}; +} + +string ZMQClient::getECDSAPublicKey(const string& keyName) { + Json::Value p; + p["type"] = ZMQMessage::GET_PUBLIC_ECDSA_REQ; + p["keyName"] = keyName; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->getECDSAPublicKey(); +} + +bool ZMQClient::generateDKGPoly(const string& polyName, int t) { + Json::Value p; + p["type"] = ZMQMessage::GENERATE_DKG_POLY_REQ; + p["polyName"] = polyName; + p["t"] = t; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + return result->getStatus() == 0; +} + +Json::Value ZMQClient::getVerificationVector(const string& polyName, int t) { + Json::Value p; + p["type"] = ZMQMessage::GET_VV_REQ; + p["polyName"] = polyName; + p["t"] = t; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->getVerificationVector(); +} + +string ZMQClient::getSecretShare(const string& polyName, const Json::Value& pubKeys, int t, int n) { + Json::Value p; + p["type"] = ZMQMessage::GET_SECRET_SHARE_REQ; + p["polyName"] = polyName; + p["publicKeys"] = pubKeys; + p["t"] = t; + p["n"] = n; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->getSecretShare(); +} + +bool ZMQClient::dkgVerification(const string& publicShares, const string& ethKeyName, + const string& secretShare, int t, int n, int idx) { + Json::Value p; + p["type"] = ZMQMessage::DKG_VERIFY_REQ; + p["ethKeyName"] = ethKeyName; + p["publicShares"] = publicShares; + p["secretShare"] = secretShare; + p["t"] = t; + p["n"] = n; + p["index"] = idx; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->isCorrect(); +} + +bool ZMQClient::createBLSPrivateKey(const string& blsKeyName, const string& ethKeyName, const string& polyName, + const string& secretShare, int t, int n) { + Json::Value p; + p["type"] = ZMQMessage::CREATE_BLS_PRIVATE_REQ; + p["ethKeyName"] = ethKeyName; + p["polyName"] = polyName; + p["blsKeyName"] = blsKeyName; + p["secretShare"] = secretShare; + p["t"] = t; + p["n"] = n; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + return result->getStatus() == 0; +} + +Json::Value ZMQClient::getBLSPublicKey(const string& blsKeyName) { + Json::Value p; + p["type"] = ZMQMessage::GET_BLS_PUBLIC_REQ; + p["blsKeyName"] = blsKeyName; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->getBLSPublicKey(); +} + +Json::Value ZMQClient::getAllBlsPublicKeys(const Json::Value& publicShares, int n, int t) { + Json::Value p; + p["type"] = ZMQMessage::GET_ALL_BLS_PUBLIC_REQ; + p["publicShares"] = publicShares["publicShares"]; + p["t"] = t; + p["n"] = n; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->getPublicKeys(); +} + +tuple ZMQClient::complaintResponse(const string& polyName, int t, int n, int idx) { + Json::Value p; + p["type"] = ZMQMessage::COMPLAINT_RESPONSE_REQ; + p["polyName"] = polyName; + p["t"] = t; + p["n"] = n; + p["ind"] = idx; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return {result->getDHKey(), result->getShare(), result->getVerificationVectorMult()}; +} + +Json::Value ZMQClient::multG2(const string& x) { + Json::Value p; + p["type"] = ZMQMessage::MULT_G2_REQ; + p["x"] = x; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->getResult(); +} + +bool ZMQClient::isPolyExists(const string& polyName) { + Json::Value p; + p["type"] = ZMQMessage::IS_POLY_EXISTS_REQ; + p["polyName"] = polyName; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->isExists(); +} + +void ZMQClient::getServerStatus() { + Json::Value p; + p["type"] = ZMQMessage::GET_SERVER_STATUS_REQ; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); +} + +string ZMQClient::getServerVersion() { + Json::Value p; + p["type"] = ZMQMessage::GET_SERVER_VERSION_REQ; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->getVersion(); +} + +bool ZMQClient::deleteBLSKey(const string& blsKeyName) { + Json::Value p; + p["type"] = ZMQMessage::DELETE_BLS_KEY_REQ; + p["blsKeyName"] = blsKeyName; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->isSuccessful(); +} + +Json::Value ZMQClient::getDecryptionShare(const string& blsKeyName, const string& publicDecryptionValue) { + Json::Value p; + p["type"] = ZMQMessage::GET_DECRYPTION_SHARE_REQ; + p["blsKeyName"] = blsKeyName; + p["publicDecryptionValue"] = publicDecryptionValue; + auto result = dynamic_pointer_cast(doRequestReply(p)); + CHECK_STATE(result); + CHECK_STATE(result->getStatus() == 0); + return result->getShare(); +} uint64_t ZMQClient::getProcessID() { return syscall(__NR_gettid); -} \ No newline at end of file +} diff --git a/ZMQClient.h b/zmq_src/ZMQClient.h similarity index 55% rename from ZMQClient.h rename to zmq_src/ZMQClient.h index 2c2e6b7e..0e10f2dd 100644 --- a/ZMQClient.h +++ b/zmq_src/ZMQClient.h @@ -1,29 +1,26 @@ /* Copyright (C) 2018-2019 SKALE Labs - This file is part of skale-consensus. + This file is part of sgxwallet. - skale-consensus is free software: you can redistribute it and/or modify + 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. - skale-consensus is distributed in the hope that it will be useful, + 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 skale-consensus. If not, see . + along with sgxwallet. If not, see . @file ZMQClient.h @author Stan Kladko @date 2021 */ - - - #ifndef SGXWALLET_ZMQCLIENT_H #define SGXWALLET_ZMQCLIENT_H @@ -34,34 +31,26 @@ #include #include "third_party/spdlog/spdlog.h" - #include #include "zhelpers.hpp" #include #include "ZMQMessage.h" - - - #define REQUEST_TIMEOUT 10000 // msecs, (> 1000!) class ZMQClient { - - private: EVP_PKEY* pkey = 0; EVP_PKEY* pubkey = 0; X509* x509Cert = 0; - bool sign = true; string certFileName = ""; string certKeyName = ""; string certificate = ""; string key = ""; - recursive_mutex mutex; zmq::context_t ctx; @@ -82,24 +71,59 @@ class ZMQClient { public: - ZMQClient(const string &ip, uint16_t port, bool _sign, const string& _certPathName, const string& _certKeyName); - void reconnect() ; + void reconnect(); static pair readPublicKeyFromCertStr(const string& _cert); static string signString(EVP_PKEY* _pkey, const string& _str); - static void verifySig(EVP_PKEY* _pubkey, const string& _str, const string& _sig); + static void verifySig(EVP_PKEY* _pubkey, const string& _str, const string& _sig); string blsSignMessageHash(const std::string &keyShareName, const std::string &messageHash, int t, int n); string ecdsaSignMessageHash(int base, const std::string &keyName, const std::string &messageHash); -}; + bool importBLSKeyShare(const std::string& keyShare, const std::string& keyName); + + string importECDSAKey(const std::string& keyShare, const std::string& keyName); + + pair generateECDSAKey(); + string getECDSAPublicKey(const string& keyName); + + bool generateDKGPoly(const string& polyName, int t); + + Json::Value getVerificationVector(const string& polyName, int t); + + string getSecretShare(const string& polyName, const Json::Value& pubKeys, int t, int n); + + bool dkgVerification(const string& publicShares, const string& ethKeyName, + const string& secretShare, int t, int n, int idx); + + bool createBLSPrivateKey(const string& blsKeyName, const string& ethKeyName, const string& polyName, + const string& secretShare, int t, int n); + + Json::Value getBLSPublicKey(const string& blsKeyName); + + Json::Value getAllBlsPublicKeys(const Json::Value& publicShares, int n, int t); + + tuple complaintResponse(const string& polyName, int t, int n, int idx); + + Json::Value multG2(const string& x); + + bool isPolyExists(const string& polyName); + + void getServerStatus(); + + string getServerVersion(); + + bool deleteBLSKey(const string& blsKeyName); + + Json::Value getDecryptionShare(const string& blsKeyName, const string& publicDecryptionValue); +}; #endif //SGXWALLET_ZMQCLIENT_H diff --git a/zmq_src/ZMQMessage.cpp b/zmq_src/ZMQMessage.cpp new file mode 100644 index 00000000..82fb8c00 --- /dev/null +++ b/zmq_src/ZMQMessage.cpp @@ -0,0 +1,361 @@ +/* + Copyright (C) 2020 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 . + + @file ZMQMessage.cpp + @author Stan Kladko + @date 2020 +*/ + +#include "common.h" +#include "sgxwallet_common.h" +#include +#include +#include + +#include "ZMQClient.h" +#include "LevelDB.h" +#include "SGXWalletServer.hpp" +#include "ReqMessage.h" +#include "RspMessage.h" +#include "ZMQMessage.h" + +uint64_t ZMQMessage::getInt64Rapid(const char *_name) { + CHECK_STATE(_name); + CHECK_STATE(d->HasMember(_name)); + const rapidjson::Value &a = (*d)[_name]; + CHECK_STATE(a.IsInt64()); + return a.GetInt64(); +}; + +Json::Value ZMQMessage::getJsonValueRapid(const char *_name) { + CHECK_STATE(_name); + CHECK_STATE(d->HasMember(_name)); + const rapidjson::Value &a = (*d)[_name]; + + rapidjson::StringBuffer buffer; + rapidjson::Writer< rapidjson::StringBuffer > writer(buffer); + a.Accept(writer); + std::string strRequest = buffer.GetString(); + + Json::Reader reader; + Json::Value root; + reader.parse(strRequest, root, false); + + return root; +} + +bool ZMQMessage::getBoolRapid(const char *_name) { + CHECK_STATE(_name); + CHECK_STATE(d->HasMember(_name)); + const rapidjson::Value &a = (*d)[_name]; + CHECK_STATE(a.IsBool()); + return a.GetBool(); +} + +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::parse(const char *_msg, + size_t _size, bool _isRequest, + bool _verifySig, bool _checkKeyOwnership) { + + CHECK_STATE(_msg); + CHECK_STATE2(_size > 5, ZMQ_INVALID_MESSAGE_SIZE); + // CHECK NULL TERMINATED + CHECK_STATE(_msg[_size] == 0); + CHECK_STATE2(_msg[_size - 1] == '}', ZMQ_INVALID_MESSAGE); + CHECK_STATE2(_msg[0] == '{', ZMQ_INVALID_MESSAGE); + + auto d = make_shared(); + + d->Parse(_msg); + + CHECK_STATE2(!d->HasParseError(), ZMQ_COULD_NOT_PARSE); + CHECK_STATE2(d->IsObject(), ZMQ_COULD_NOT_PARSE); + + CHECK_STATE2(d->HasMember("type"), ZMQ_NO_TYPE_IN_MESSAGE); + CHECK_STATE2((*d)["type"].IsString(), ZMQ_NO_TYPE_IN_MESSAGE); + string type = (*d)["type"].GetString(); + + if (_verifySig) { + CHECK_STATE2(d->HasMember("cert"),ZMQ_NO_CERT_IN_MESSAGE); + CHECK_STATE2(d->HasMember("msgSig"), ZMQ_NO_SIG_IN_MESSAGE); + CHECK_STATE2((*d)["cert"].IsString(), ZMQ_NO_CERT_IN_MESSAGE); + CHECK_STATE2((*d)["msgSig"].IsString(), ZMQ_NO_SIG_IN_MESSAGE); + + auto cert = make_shared((*d)["cert"].GetString()); + string hash = cryptlite::sha256::hash_hex(*cert); + + auto filepath = "/tmp/sgx_wallet_cert_hash_" + hash; + + std::ofstream outFile(filepath); + + outFile << *cert; + + outFile.close(); + + static recursive_mutex m; + + EVP_PKEY *publicKey = nullptr; + + { + lock_guard lock(m); + + if (!verifiedCerts.exists(*cert)) { + CHECK_STATE(SGXWalletServer::verifyCert(filepath)); + auto handles = ZMQClient::readPublicKeyFromCertStr(*cert); + CHECK_STATE(handles.first); + CHECK_STATE(handles.second); + verifiedCerts.put(*cert, handles); + remove(cert->c_str()); + } + + publicKey = verifiedCerts.get(*cert).first; + + CHECK_STATE(publicKey); + + auto msgSig = make_shared((*d)["msgSig"].GetString()); + + d->RemoveMember("msgSig"); + + rapidjson::StringBuffer buffer; + + rapidjson::Writer w(buffer); + + d->Accept(w); + + auto msgToVerify = buffer.GetString(); + + ZMQClient::verifySig(publicKey, msgToVerify, *msgSig ); + + } + } + + if (_isRequest) { + return buildRequest(type, d, _checkKeyOwnership); + } else { + return buildResponse(type, d, _checkKeyOwnership); + } +} + +shared_ptr ZMQMessage::buildRequest(string &_type, shared_ptr _d, + bool _checkKeyOwnership) { + Requests r; + try { + int t = requests.at( _type ); + r = static_cast(t); + } catch ( std::out_of_range& ) { + BOOST_THROW_EXCEPTION(SGXException(-301, "Incorrect zmq message type: " + string(_type))); + } + + shared_ptr ret = nullptr; + + switch (r) { + case ENUM_BLS_SIGN_REQ: + ret = make_shared(_d); + break; + case ENUM_ECDSA_SIGN_REQ: + ret = make_shared(_d); + break; + case ENUM_IMPORT_BLS_REQ: + ret = make_shared(_d); + break; + case ENUM_IMPORT_ECDSA_REQ: + ret = make_shared(_d); + break; + case ENUM_GENERATE_ECDSA_REQ: + ret = make_shared(_d); + break; + case ENUM_GET_PUBLIC_ECDSA_REQ: + ret = make_shared(_d); + break; + case ENUM_GENERATE_DKG_POLY_REQ: + ret = make_shared(_d); + break; + case ENUM_GET_VV_REQ: + ret = make_shared(_d); + break; + case ENUM_GET_SECRET_SHARE_REQ: + ret = make_shared(_d); + break; + case ENUM_DKG_VERIFY_REQ: + ret = make_shared(_d); + break; + case ENUM_CREATE_BLS_PRIVATE_REQ: + ret = make_shared(_d); + break; + case ENUM_GET_BLS_PUBLIC_REQ: + ret = make_shared(_d); + break; + case ENUM_GET_ALL_BLS_PUBLIC_REQ: + ret = make_shared(_d); + break; + case ENUM_COMPLAINT_RESPONSE_REQ: + ret = make_shared(_d); + break; + case ENUM_MULT_G2_REQ: + ret = make_shared(_d); + break; + case ENUM_IS_POLY_EXISTS_REQ: + ret = make_shared(_d); + break; + case ENUM_GET_SERVER_STATUS_REQ: + ret = make_shared(_d); + break; + case ENUM_GET_SERVER_VERSION_REQ: + ret = make_shared(_d); + break; + case ENUM_DELETE_BLS_KEY_REQ: + ret = make_shared(_d); + break; + case ENUM_GET_DECRYPTION_SHARE_REQ: + ret = make_shared(_d); + break; + default: + break; + } + + ret->setCheckKeyOwnership(_checkKeyOwnership); + + return ret; +} + +shared_ptr ZMQMessage::buildResponse(string &_type, shared_ptr _d, + bool _checkKeyOwnership) { + Responses r; + try { + int t = responses.at( _type ); + r = static_cast(t); + } catch ( std::out_of_range& ) { + BOOST_THROW_EXCEPTION(InvalidStateException("Incorrect zmq message request type: " + string(_type), + __CLASS_NAME__) + ); + } + + shared_ptr ret = nullptr; + + switch (r) { + case ENUM_BLS_SIGN_RSP: + ret = make_shared(_d); + break; + case ENUM_ECDSA_SIGN_RSP: + ret = make_shared(_d); + break; + case ENUM_IMPORT_BLS_RSP: + ret = make_shared(_d); + break; + case ENUM_IMPORT_ECDSA_RSP: + ret = make_shared(_d); + break; + case ENUM_GENERATE_ECDSA_RSP: + ret = make_shared(_d); + break; + case ENUM_GET_PUBLIC_ECDSA_RSP: + ret = make_shared(_d); + break; + case ENUM_GENERATE_DKG_POLY_RSP: + ret = make_shared(_d); + break; + case ENUM_GET_VV_RSP: + ret = make_shared(_d); + break; + case ENUM_GET_SECRET_SHARE_RSP: + ret = make_shared(_d); + break; + case ENUM_DKG_VERIFY_RSP: + ret = make_shared(_d); + break; + case ENUM_CREATE_BLS_PRIVATE_RSP: + ret = make_shared(_d); + break; + case ENUM_GET_BLS_PUBLIC_RSP: + ret = make_shared(_d); + break; + case ENUM_GET_ALL_BLS_PUBLIC_RSP: + ret = make_shared(_d); + break; + case ENUM_COMPLAINT_RESPONSE_RSP: + ret = make_shared(_d); + break; + case ENUM_MULT_G2_RSP: + ret = make_shared(_d); + break; + case ENUM_IS_POLY_EXISTS_RSP: + ret = make_shared(_d); + break; + case ENUM_GET_SERVER_STATUS_RSP: + ret = make_shared(_d); + break; + case ENUM_GET_SERVER_VERSION_RSP: + ret = make_shared(_d); + break; + case ENUM_DELETE_BLS_KEY_RSP: + ret = make_shared(_d); + break; + case ENUM_GET_DECRYPTION_SHARE_RSP: + ret = make_shared(_d); + break; + default: + break; + } + + ret->setCheckKeyOwnership(_checkKeyOwnership); + + return ret; +} + +std::map ZMQMessage::keysByOwners; + +bool ZMQMessage::isKeyByOwner(const string& keyName, const string& cert) { + auto value = LevelDB::getLevelDb()->readString(keyName + ":OWNER"); + return value && *value == cert; +} + +void ZMQMessage::addKeyByOwner(const string& keyName, const string& cert) { + SGXWalletServer::writeDataToDB(keyName + ":OWNER", cert); +} + +bool ZMQMessage::isKeyRegistered(const string& keyName) { + return LevelDB::getLevelDb()->readString(keyName + ":OWNER") != nullptr; +} + +cache::lru_cache> ZMQMessage::verifiedCerts(256); + +const std::map ZMQMessage::requests{ + {BLS_SIGN_REQ, 0}, {ECDSA_SIGN_REQ, 1}, {IMPORT_BLS_REQ, 2}, {IMPORT_ECDSA_REQ, 3}, + {GENERATE_ECDSA_REQ, 4}, {GET_PUBLIC_ECDSA_REQ, 5}, {GENERATE_DKG_POLY_REQ, 6}, + {GET_VV_REQ, 7}, {GET_SECRET_SHARE_REQ, 8}, {DKG_VERIFY_REQ, 9}, + {CREATE_BLS_PRIVATE_REQ, 10}, {GET_BLS_PUBLIC_REQ, 11}, {GET_ALL_BLS_PUBLIC_REQ, 12}, + {COMPLAINT_RESPONSE_REQ, 13}, {MULT_G2_REQ, 14}, {IS_POLY_EXISTS_REQ, 15}, + {GET_SERVER_STATUS_REQ, 16}, {GET_SERVER_VERSION_REQ, 17}, {DELETE_BLS_KEY_REQ, 18}, + {GET_DECRYPTION_SHARE_REQ, 19} +}; + +const std::map ZMQMessage::responses { + {BLS_SIGN_RSP, 0}, {ECDSA_SIGN_RSP, 1}, {IMPORT_BLS_RSP, 2}, {IMPORT_ECDSA_RSP, 3}, + {GENERATE_ECDSA_RSP, 4}, {GET_PUBLIC_ECDSA_RSP, 5}, {GENERATE_DKG_POLY_RSP, 6}, + {GET_VV_RSP, 7}, {GET_SECRET_SHARE_RSP, 8}, {DKG_VERIFY_RSP, 9}, + {CREATE_BLS_PRIVATE_RSP, 10}, {GET_BLS_PUBLIC_RSP, 11}, {GET_ALL_BLS_PUBLIC_RSP, 12}, + {COMPLAINT_RESPONSE_RSP, 13}, {MULT_G2_RSP, 14}, {IS_POLY_EXISTS_RSP, 15}, + {GET_SERVER_STATUS_RSP, 16}, {GET_SERVER_VERSION_RSP, 17}, {DELETE_BLS_KEY_RSP, 18}, + {GET_DECRYPTION_SHARE_RSP, 19} +}; diff --git a/zmq_src/ZMQMessage.h b/zmq_src/ZMQMessage.h new file mode 100644 index 00000000..45051634 --- /dev/null +++ b/zmq_src/ZMQMessage.h @@ -0,0 +1,151 @@ +/* + 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 . + + @file ZMQMessage.h + @author Stan Kladko + @date 2018 +*/ + +#pragma once + + +#include +#include +#include +#include +#include + +#include "third_party/lrucache.hpp" + +#include "abstractstubserver.h" + +#include "document.h" +#include "stringbuffer.h" +#include "writer.h" + +#include "SGXException.h" + +using namespace std; + +class ZMQMessage { + + shared_ptr d; + + static cache::lru_cache> verifiedCerts; + +protected: + bool checkKeyOwnership = true; + + static std::map keysByOwners; + + static bool isKeyByOwner(const string& keyName, const string& cert); + + static void addKeyByOwner(const string& keyName, const string& cert); + + static bool isKeyRegistered(const std::string& keyName); + +public: + + 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"; + static constexpr const char *IMPORT_BLS_REQ = "importBLSReq"; + static constexpr const char *IMPORT_BLS_RSP = "importBLSRps"; + static constexpr const char *IMPORT_ECDSA_REQ = "importECDSAReq"; + static constexpr const char *IMPORT_ECDSA_RSP = "importECDSARsp"; + static constexpr const char *GENERATE_ECDSA_REQ = "generateECDSAReq"; + static constexpr const char *GENERATE_ECDSA_RSP = "generateECDSARsp"; + static constexpr const char *GET_PUBLIC_ECDSA_REQ = "getPublicECDSAReq"; + static constexpr const char *GET_PUBLIC_ECDSA_RSP = "getPublicECDSARsp"; + static constexpr const char *GENERATE_DKG_POLY_REQ = "generateDKGPolyReq"; + static constexpr const char *GENERATE_DKG_POLY_RSP = "generateDKGPolyRsp"; + static constexpr const char *GET_VV_REQ = "getVerificationVectorReq"; + static constexpr const char *GET_VV_RSP = "getVerificationVectorRsp"; + static constexpr const char *GET_SECRET_SHARE_REQ = "getSecretShareReq"; + static constexpr const char *GET_SECRET_SHARE_RSP = "getSecretShareRsp"; + static constexpr const char *DKG_VERIFY_REQ = "dkgVerificationReq"; + static constexpr const char *DKG_VERIFY_RSP = "dkgVerificationRsp"; + static constexpr const char *CREATE_BLS_PRIVATE_REQ = "createBLSPrivateReq"; + static constexpr const char *CREATE_BLS_PRIVATE_RSP = "createBLSPrivateRsp"; + static constexpr const char *GET_BLS_PUBLIC_REQ = "getBLSPublicReq"; + static constexpr const char *GET_BLS_PUBLIC_RSP = "getBLSPublicRsp"; + static constexpr const char *GET_ALL_BLS_PUBLIC_REQ = "getAllBLSPublicReq"; + static constexpr const char *GET_ALL_BLS_PUBLIC_RSP = "getAllBLSPublicRsp"; + static constexpr const char *COMPLAINT_RESPONSE_REQ = "complaintResponseReq"; + static constexpr const char *COMPLAINT_RESPONSE_RSP = "complaintResponseRsp"; + static constexpr const char *MULT_G2_REQ = "multG2Req"; + static constexpr const char *MULT_G2_RSP = "multG2Rsp"; + static constexpr const char *IS_POLY_EXISTS_REQ = "isPolyExistsReq"; + static constexpr const char *IS_POLY_EXISTS_RSP = "isPolyExistsRsp"; + static constexpr const char *GET_SERVER_STATUS_REQ = "getServerStatusReq"; + static constexpr const char *GET_SERVER_STATUS_RSP = "getServerStatusRsp"; + static constexpr const char *GET_SERVER_VERSION_REQ = "getServerVersionReq"; + static constexpr const char *GET_SERVER_VERSION_RSP = "getServerVersionRsp"; + static constexpr const char *DELETE_BLS_KEY_REQ = "deleteBLSKeyReq"; + static constexpr const char *DELETE_BLS_KEY_RSP = "deleteBLSKeyRsp"; + static constexpr const char *GET_DECRYPTION_SHARE_REQ = "getDecryptionShareReq"; + static constexpr const char *GET_DECRYPTION_SHARE_RSP = "getDecryptionShareRsp"; + + static const std::map requests; + static const std::map responses; + + enum Requests { ENUM_BLS_SIGN_REQ, ENUM_ECDSA_SIGN_REQ, ENUM_IMPORT_BLS_REQ, ENUM_IMPORT_ECDSA_REQ, ENUM_GENERATE_ECDSA_REQ, ENUM_GET_PUBLIC_ECDSA_REQ, + ENUM_GENERATE_DKG_POLY_REQ, ENUM_GET_VV_REQ, ENUM_GET_SECRET_SHARE_REQ, ENUM_DKG_VERIFY_REQ, ENUM_CREATE_BLS_PRIVATE_REQ, + ENUM_GET_BLS_PUBLIC_REQ, ENUM_GET_ALL_BLS_PUBLIC_REQ, ENUM_COMPLAINT_RESPONSE_REQ, ENUM_MULT_G2_REQ, ENUM_IS_POLY_EXISTS_REQ, + ENUM_GET_SERVER_STATUS_REQ, ENUM_GET_SERVER_VERSION_REQ, ENUM_DELETE_BLS_KEY_REQ, ENUM_GET_DECRYPTION_SHARE_REQ }; + enum Responses { ENUM_BLS_SIGN_RSP, ENUM_ECDSA_SIGN_RSP, ENUM_IMPORT_BLS_RSP, ENUM_IMPORT_ECDSA_RSP, ENUM_GENERATE_ECDSA_RSP, ENUM_GET_PUBLIC_ECDSA_RSP, + ENUM_GENERATE_DKG_POLY_RSP, ENUM_GET_VV_RSP, ENUM_GET_SECRET_SHARE_RSP, ENUM_DKG_VERIFY_RSP, ENUM_CREATE_BLS_PRIVATE_RSP, + ENUM_GET_BLS_PUBLIC_RSP, ENUM_GET_ALL_BLS_PUBLIC_RSP, ENUM_COMPLAINT_RESPONSE_RSP, ENUM_MULT_G2_RSP, ENUM_IS_POLY_EXISTS_RSP, + ENUM_GET_SERVER_STATUS_RSP, ENUM_GET_SERVER_VERSION_RSP, ENUM_DELETE_BLS_KEY_RSP, ENUM_GET_DECRYPTION_SHARE_RSP }; + + explicit ZMQMessage(shared_ptr &_d) : d(_d) {}; + + string getStringRapid(const char *_name); + + uint64_t getInt64Rapid(const char *_name); + + Json::Value getJsonValueRapid(const char *_name); + + bool getBoolRapid(const char *_name); + + uint64_t getStatus() { + return getInt64Rapid("status"); + } + + std::string rapidToString() { + rapidjson::StringBuffer buffer; + rapidjson::Writer< rapidjson::StringBuffer > writer( buffer ); + d->Accept( writer ); + std::string strRequest = buffer.GetString(); + return strRequest; + } + + static shared_ptr parse(const char* _msg, size_t _size, bool _isRequest, + bool _verifySig, bool _checkKeyOwnership); + + static shared_ptr buildRequest(string& type, shared_ptr _d, + bool _checkKeyOwnership); + static shared_ptr buildResponse(string& type, shared_ptr _d, + bool _checkKeyOwnership); + + virtual Json::Value process() = 0; + + void setCheckKeyOwnership(bool _check) { checkKeyOwnership = _check; } + +}; diff --git a/zmq_src/ZMQServer.cpp b/zmq_src/ZMQServer.cpp new file mode 100644 index 00000000..44de4491 --- /dev/null +++ b/zmq_src/ZMQServer.cpp @@ -0,0 +1,403 @@ +/* + 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 . + + @file ZMQServer.cpp + @author Stan Kladko + @date 2019 +*/ + +#include +#include + +#include + +#include "third_party/spdlog/spdlog.h" + +#include "common.h" +#include "sgxwallet_common.h" + +#include "SGXException.h" +#include "ExitRequestedException.h" +#include "ReqMessage.h" +#include "ZMQMessage.h" +#include "ZMQServer.h" + + +using namespace std; + +shared_ptr ZMQServer::zmqServer = nullptr; + +ZMQServer::ZMQServer(bool _checkSignature, bool _checkKeyOwnership, const string &_caCertFile) + : incomingQueue(NUM_ZMQ_WORKER_THREADS), checkSignature(_checkSignature), checkKeyOwnership(_checkKeyOwnership), + caCertFile(_caCertFile), ctx(make_shared(1)) { + + CHECK_STATE(NUM_ZMQ_WORKER_THREADS > 1); + + socket = make_shared(*ctx, ZMQ_ROUTER); + + if (_checkSignature) { + CHECK_STATE(!_caCertFile.empty()); + ifstream t(_caCertFile); + string str((istreambuf_iterator(t)), istreambuf_iterator()); + caCert = str; + CHECK_STATE(!caCert.empty()) + } + + int linger = 0; + + zmq_setsockopt(*socket, ZMQ_LINGER, &linger, sizeof(linger)); + + threadPool = make_shared(NUM_ZMQ_WORKER_THREADS, this); + +} + +void ZMQServer::initListenSocket() { + + auto port = BASE_PORT + 5; + + spdlog::info("Starting zmq server on port {} ...", port); + + try { + CHECK_STATE(socket); + socket->bind("tcp://*:" + to_string(port)); + } catch (...) { + spdlog::error("Zmq server task could not bind to port:{}", port); + throw SGXException(ZMQ_COULD_NOT_BIND_FRONT_END, "Server task could not bind."); + } + + spdlog::info("ZMQ server socket created and bound."); + +} + +void ZMQServer::run() { + + + zmqServer->initListenSocket(); + + spdlog::info("Started zmq read loop."); + + while (!isExitRequested) { + try { + zmqServer->doOneServerLoop(); + } catch (ExitRequestedException &e) { + spdlog::info("Exit requested. Exiting server loop"); + break; + } + catch (...) { + spdlog::error("doOneServerLoop threw exception. This should never happen!"); + } + } + + spdlog::info("Exited zmq server loop"); +} + +atomic ZMQServer::isExitRequested(false); + +void ZMQServer::exitZMQServer() { + // if already exited do not exit + spdlog::info("exitZMQServer called"); + if (isExitRequested.exchange(true)) { + spdlog::info("Exit is already under way"); + return; + } + + spdlog::info("Exiting ZMQServer"); + spdlog::info("Joining worker thread pool threads ..."); + zmqServer->threadPool->joinAll(); + spdlog::info("Joined worker thread pool threads"); + spdlog::info("Shutting down ZMQ contect"); + zmqServer->ctx->shutdown(); + spdlog::info("Shut down ZMQ contect"); + spdlog::info("Closing ZMQ server socket ..."); + zmqServer->socket->close(); + spdlog::info("Closed ZMQ server socket"); + spdlog::info("Closing ZMQ context ..."); + zmqServer->ctx->close(); + spdlog::info("Closed ZMQ context."); + spdlog::info("Exited zmq server."); +} + +void ZMQServer::initZMQServer(bool _checkSignature, bool _checkKeyOwnership) { + static bool initedServer = false; + CHECK_STATE(!initedServer) + initedServer = true; + + spdlog::info("Initing zmq server.\n checkSignature is set to {}.\n checkKeyOwnership is set to {}", + _checkSignature, _checkKeyOwnership); + + string rootCAPath = ""; + + if (_checkSignature) { + rootCAPath = string(SGXDATA_FOLDER) + "cert_data/rootCA.pem"; + spdlog::info("Reading root CA from {}", rootCAPath); + CHECK_STATE(access(rootCAPath.c_str(), F_OK) == 0); + spdlog::info("Read CA.", rootCAPath); + }; + + spdlog::info("Initing zmq server ..."); + + zmqServer = make_shared(_checkSignature, _checkKeyOwnership, rootCAPath); + + CHECK_STATE(zmqServer) + serverThread = make_shared(bind(&ZMQServer::run, ZMQServer::zmqServer)); + serverThread->detach(); + + spdlog::info("Releasing SGX worker threads ..."); + + zmqServer->releaseWorkers(); + + spdlog::info("Released SGX worker threads."); + + spdlog::info("Inited zmq server."); +} + +shared_ptr ZMQServer::serverThread = nullptr; + +ZMQServer::~ZMQServer() { + exitZMQServer(); +} + +void ZMQServer::checkForExit() { + if (isExitRequested) { + throw ExitRequestedException(); + } +} + + +void ZMQServer::sendMessagesInOutgoingMessageQueueIfAny() { + pair > element; + + // send all items in outgoing queue + while (outgoingQueue.try_dequeue(element)) { + sendToClient(element.first, element.second); + } +} + +void ZMQServer::waitForIncomingAndProcessOutgoingMessages() { + zmq_pollitem_t items[1]; + items[0].socket = *socket; + items[0].events = ZMQ_POLLIN; + + int pollResult = 0; + + do { + checkForExit(); + pollResult = zmq_poll(items, 1, 1); + + sendMessagesInOutgoingMessageQueueIfAny(); + + } while (pollResult == 0); + +} + +pair > ZMQServer::receiveMessage() { + + auto identity = make_shared(); + + if (!socket->recv(identity.get())) { + checkForExit(); + // something terrible happened + spdlog::error("Fatal error: socket->recv(&identity) returned false. Exiting."); + exit(-11); + } + + if (!identity->more()) { + checkForExit(); + // something terrible happened + spdlog::error("Fatal error: zmq_msg_more(identity) returned false. Existing."); + exit(-12); + } + + auto reqMsg = make_shared(); + + if (!socket->recv(reqMsg.get(), 0)) { + checkForExit(); + // something terrible happened + spdlog::error("Fatal error: socket.recv(&reqMsg, 0) returned false. Exiting"); + exit(-13); + } + + auto result = string((char *) reqMsg->data(), reqMsg->size()); + + CHECK_STATE(result.front() == '{') + CHECK_STATE(result.back() == '}') + return {result, identity}; +} + +void ZMQServer::sendToClient(Json::Value &_result, shared_ptr &_identity) { + string replyStr; + try { + Json::FastWriter fastWriter; + fastWriter.omitEndingLineFeed(); + + replyStr = fastWriter.write(_result); + + CHECK_STATE(replyStr.size() > 2); + CHECK_STATE(replyStr.front() == '{'); + CHECK_STATE(replyStr.back() == '}'); + + if (!socket->send(*_identity, ZMQ_SNDMORE)) { + exit(-15); + } + if (!s_send(*socket, replyStr)) { + exit(-16); + } + } catch (ExitRequestedException) { + throw; + } catch (exception &e) { + checkForExit(); + spdlog::error("Exception in zmq server worker send :{}", e.what()); + exit(-17); + } catch (...) { + checkForExit(); + spdlog::error("Unklnown exception in zmq server worker send"); + exit(-18); + } + +} + +void ZMQServer::doOneServerLoop() { + + Json::Value result; + result["status"] = ZMQ_SERVER_ERROR; + + shared_ptr identity = nullptr; + string msgStr; + + try { + + waitForIncomingAndProcessOutgoingMessages(); + + tie(msgStr, identity) = receiveMessage(); + + { + + auto msg = ZMQMessage::parse( + msgStr.c_str(), msgStr.size(), true, checkSignature, checkKeyOwnership); + + CHECK_STATE2(msg, ZMQ_COULD_NOT_PARSE); + + uint64_t index = 0; + + if ((dynamic_pointer_cast(msg)) || + dynamic_pointer_cast(msg)) { + + boost::hash string_hash; + + auto hash = string_hash(string((const char *) identity->data())); + + index = hash % (NUM_ZMQ_WORKER_THREADS - 1); + } else { + index = NUM_ZMQ_WORKER_THREADS - 1; + } + + auto element = pair < shared_ptr < ZMQMessage >, shared_ptr> + (msg, identity); + + + incomingQueue.at(index).enqueue(element); + } + + } catch (ExitRequestedException) { + throw; + } catch (exception &e) { + checkForExit(); + result["errorMessage"] = string(e.what()); + spdlog::error("Exception in zmq server :{}", e.what()); + spdlog::error("ID:" + string((char *) identity->data(), identity->size())); + spdlog::error("Client request :" + msgStr); + sendToClient(result, identity); + } catch (...) { + checkForExit(); + spdlog::error("Error in zmq server "); + result["errorMessage"] = "Error in zmq server "; + spdlog::error("ID:" + string((char *) identity->data(), identity->size())); + spdlog::error("Client request :" + msgStr); + sendToClient(result, identity); + } + + +} + +void ZMQServer::workerThreadProcessNextMessage(uint64_t _threadNumber) { + + + Json::Value result; + result["status"] = ZMQ_SERVER_ERROR; + string msgStr; + + pair , shared_ptr> element; + + try { + while (!incomingQueue.at(_threadNumber) + .wait_dequeue_timed(element, std::chrono::milliseconds(1000))) { + checkForExit(); + } + } catch (ExitRequestedException) { + throw; + } catch (exception &e) { + checkForExit(); + result["errorMessage"] = string(e.what()); + spdlog::error("Exception in zmq server :{}", e.what()); + spdlog::error("Client request :" + msgStr); + } catch (...) { + checkForExit(); + spdlog::error("Error in zmq server "); + result["errorMessage"] = "Error in zmq server "; + spdlog::error("Client request :" + msgStr); + } + + try { + result = element.first->process(); + } catch (ExitRequestedException) { + throw; + } catch (exception &e) { + checkForExit(); + result["errorMessage"] = string(e.what()); + spdlog::error("Exception in zmq server :{}", e.what()); + spdlog::error("ID:" + string((char *) element.second->data(), element.second->size())); + spdlog::error("Client request :" + msgStr); + } catch (...) { + checkForExit(); + spdlog::error("Error in zmq server "); + result["errorMessage"] = "Error in zmq server "; + spdlog::error("ID:" + string((char *) element.second->data(), element.second->size())); + spdlog::error("Client request :" + msgStr); + } + + pair > fullResult(result, element.second); + + outgoingQueue.enqueue(fullResult); +} + +void ZMQServer::workerThreadMessageProcessLoop(ZMQServer *_agent, uint64_t _threadNumber) { + CHECK_STATE(_agent); + _agent->waitOnGlobalStartBarrier(); + // do work forever until told to exit + while (!isExitRequested) { + try { + _agent->workerThreadProcessNextMessage(_threadNumber); + } catch (ExitRequestedException &e) { + break; + } catch (Exception &e) { + spdlog::error(string("Caught exception in worker thread loop:") + e.what()); + } + } + + spdlog::info("Exit requested. Exiting worker thread:" + to_string(_threadNumber)); +} \ No newline at end of file diff --git a/zmq_src/ZMQServer.h b/zmq_src/ZMQServer.h new file mode 100644 index 00000000..c5e95fcb --- /dev/null +++ b/zmq_src/ZMQServer.h @@ -0,0 +1,103 @@ +/* + 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 . + + @file ZMQServer.h + @author Stan Kladko + @date 2020 +*/ + + +#ifndef SGXWALLET_ZMQServer_H +#define SGXWALLET_ZMQServer_H + + +#include "third_party/readerwriterqueue.h" +#include "third_party/concurrentqueue.h" + + +#include +#include "zhelpers.hpp" + +#include "Agent.h" +#include "WorkerThreadPool.h" +#include "ZMQMessage.h" + +using namespace moodycamel; + +static const uint64_t NUM_ZMQ_WORKER_THREADS = 16; + + +class ZMQServer : public Agent{ + + uint64_t workerThreads; + + string caCertFile; + string caCert; + + ConcurrentQueue>> outgoingQueue; + + vector, shared_ptr>>> incomingQueue; + + bool checkKeyOwnership = true; + + shared_ptr ctx; + shared_ptr socket; + + static atomic isExitRequested; + + void doOneServerLoop(); + +public: + + bool checkSignature = false; + + static shared_ptr zmqServer; + + shared_ptr threadPool = nullptr; + + static shared_ptr serverThread; + + ZMQServer(bool _checkSignature, bool _checkKeyOwnership, const string& _caCertFile); + + ~ZMQServer(); + + void run(); + + void initListenSocket(); + + static void initZMQServer(bool _checkSignature, bool _checkKeyOwnership); + static void exitZMQServer(); + + static void workerThreadMessageProcessLoop(ZMQServer* agent, uint64_t _threadNumber ); + + void workerThreadProcessNextMessage(uint64_t _threadNumber); + + void checkForExit(); + + void waitForIncomingAndProcessOutgoingMessages(); + + pair> receiveMessage(); + + void sendToClient(Json::Value& _result, shared_ptr& _identity); + + void sendMessagesInOutgoingMessageQueueIfAny(); + +}; + + +#endif //SGXWALLET_ZMQServer_H