/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with OpenSSL (or a modified version of that library), containing parts
* covered by the terms of OpenSSL License and SSLeay License, the licensors
* of this Program grant you additional permission to convey the resulting work.
*
*/
#include
#include
#include
#include
#include "jpsock.hpp"
#include "socks.hpp"
#include "socket.hpp"
#include "xmrstak/misc/executor.hpp"
#include "xmrstak/jconf.hpp"
#include "xmrstak/misc/jext.hpp"
#include "xmrstak/version.hpp"
using namespace rapidjson;
struct jpsock::call_rsp
{
bool bHaveResponse;
uint64_t iCallId;
Value* pCallData;
std::string sCallErr;
call_rsp(Value* val) : pCallData(val)
{
bHaveResponse = false;
iCallId = 0;
sCallErr.clear();
}
};
typedef GenericDocument, MemoryPoolAllocator<>, MemoryPoolAllocator<>> MemDocument;
/*
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* ASSUMPTION - only one calling thread. Multiple calling threads would require better
* thread safety. The calling thread is assumed to be the executor thread.
* If there is a reason to call the pool outside of the executor context, consider
* doing it via an executor event.
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* Call values and allocators are for the calling thread (executor). When processing
* a call, the recv thread will make a copy of the call response and then erase its copy.
*/
struct jpsock::opaque_private
{
Value oCallValue;
MemoryPoolAllocator<> callAllocator;
MemoryPoolAllocator<> recvAllocator;
MemoryPoolAllocator<> parseAllocator;
MemDocument jsonDoc;
call_rsp oCallRsp;
opaque_private(uint8_t* bCallMem, uint8_t* bRecvMem, uint8_t* bParseMem) :
callAllocator(bCallMem, jpsock::iJsonMemSize),
recvAllocator(bRecvMem, jpsock::iJsonMemSize),
parseAllocator(bParseMem, jpsock::iJsonMemSize),
jsonDoc(&recvAllocator, jpsock::iJsonMemSize, &parseAllocator),
oCallRsp(nullptr)
{
}
};
struct jpsock::opq_json_val
{
const Value* val;
opq_json_val(const Value* val) : val(val) {}
};
jpsock::jpsock(size_t id, const char* sAddr, const char* sLogin, const char* sRigId, const char* sPassword, double pool_weight, bool dev_pool, bool tls, const char* tls_fp, bool nicehash) :
net_addr(sAddr), usr_login(sLogin), usr_rigid(sRigId), usr_pass(sPassword), tls_fp(tls_fp), pool_id(id), pool_weight(pool_weight), pool(dev_pool), nicehash(nicehash),
connect_time(0), connect_attempts(0), disconnect_time(0), quiet_close(false)
{
sock_init();
bJsonCallMem = (uint8_t*)malloc(iJsonMemSize);
bJsonRecvMem = (uint8_t*)malloc(iJsonMemSize);
bJsonParseMem = (uint8_t*)malloc(iJsonMemSize);
prv = new opaque_private(bJsonCallMem, bJsonRecvMem, bJsonParseMem);
#ifndef CONF_NO_TLS
if(tls)
sck = new tls_socket(this);
else
sck = new plain_socket(this);
#else
sck = new plain_socket(this);
#endif
oRecvThd = nullptr;
bRunning = false;
bLoggedIn = false;
iJobDiff = 0;
memset(&oCurrentJob, 0, sizeof(oCurrentJob));
}
jpsock::~jpsock()
{
delete prv;
prv = nullptr;
free(bJsonCallMem);
free(bJsonRecvMem);
free(bJsonParseMem);
}
std::string&& jpsock::get_call_error()
{
call_error = false;
return std::move(prv->oCallRsp.sCallErr);
}
bool jpsock::set_socket_error(const char* a)
{
if(!bHaveSocketError)
{
bHaveSocketError = true;
sSocketError.assign(a);
}
return false;
}
bool jpsock::set_socket_error(const char* a, const char* b)
{
if(!bHaveSocketError)
{
bHaveSocketError = true;
size_t ln_a = strlen(a);
size_t ln_b = strlen(b);
sSocketError.reserve(ln_a + ln_b + 2);
sSocketError.assign(a, ln_a);
sSocketError.append(b, ln_b);
}
return false;
}
bool jpsock::set_socket_error(const char* a, size_t len)
{
if(!bHaveSocketError)
{
bHaveSocketError = true;
sSocketError.assign(a, len);
}
return false;
}
bool jpsock::set_socket_error_strerr(const char* a)
{
char sSockErrText[512];
return set_socket_error(a, sock_strerror(sSockErrText, sizeof(sSockErrText)));
}
bool jpsock::set_socket_error_strerr(const char* a, int res)
{
char sSockErrText[512];
return set_socket_error(a, sock_gai_strerror(res, sSockErrText, sizeof(sSockErrText)));
}
void jpsock::jpsock_thread()
{
jpsock_thd_main();
if(!bHaveSocketError)
set_socket_error("Socket closed.");
executor::inst()->push_event(ex_event(std::move(sSocketError), quiet_close, pool_id));
std::unique_lock mlock(call_mutex);
bool bWait = prv->oCallRsp.pCallData != nullptr;
// If a call is waiting, wait a little bit before blowing it out of the water
if(bWait)
{
mlock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
mlock.lock();
}
// If the call is still there send an error to end it
bool bCallWaiting = false;
if(prv->oCallRsp.pCallData != nullptr)
{
prv->oCallRsp.bHaveResponse = true;
prv->oCallRsp.iCallId = 0;
prv->oCallRsp.pCallData = nullptr;
bCallWaiting = true;
}
mlock.unlock();
if(bCallWaiting)
call_cond.notify_one();
bLoggedIn = false;
if(bHaveSocketError && !quiet_close)
disconnect_time = get_timestamp();
else
disconnect_time = 0;
std::unique_lock lck(job_mutex);
memset(&oCurrentJob, 0, sizeof(oCurrentJob));
bRunning = false;
}
bool jpsock::jpsock_thd_main()
{
if(!sck->connect())
return false;
executor::inst()->push_event(ex_event(EV_SOCK_READY, pool_id));
char buf[iSockBufferSize];
size_t datalen = 0;
while (true)
{
int ret = sck->recv(buf + datalen, sizeof(buf) - datalen);
if(ret <= 0)
return false;
datalen += ret;
if (datalen >= sizeof(buf))
{
sck->close(false);
return set_socket_error("RECEIVE error: data overflow");
}
char* lnend;
char* lnstart = buf;
while ((lnend = (char*)memchr(lnstart, '\n', datalen)) != nullptr)
{
lnend++;
int lnlen = lnend - lnstart;
if (!process_line(lnstart, lnlen))
{
sck->close(false);
return false;
}
datalen -= lnlen;
lnstart = lnend;
}
//Got leftover data? Move it to the front
if (datalen > 0 && buf != lnstart)
memmove(buf, lnstart, datalen);
}
}
bool jpsock::process_line(char* line, size_t len)
{
prv->jsonDoc.SetNull();
prv->parseAllocator.Clear();
prv->callAllocator.Clear();
/*NULL terminate the line instead of '\n', parsing will add some more NULLs*/
line[len-1] = '\0';
//printf("RECV: %s\n", line);
if (prv->jsonDoc.ParseInsitu(line).HasParseError())
return set_socket_error("PARSE error: Invalid JSON");
if (!prv->jsonDoc.IsObject())
return set_socket_error("PARSE error: Invalid root");
const Value* mt;
if (prv->jsonDoc.HasMember("method"))
{
mt = GetObjectMember(prv->jsonDoc, "method");
if(!mt->IsString())
return set_socket_error("PARSE error: Protocol error 1");
if(strcmp(mt->GetString(), "mining.set_extranonce") == 0)
{
printer::inst()->print_msg(L0, "Detected buggy NiceHash pool code. Workaround engaged.");
return true;
}
if(strcmp(mt->GetString(), "job") != 0)
return set_socket_error("PARSE error: Unsupported server method ", mt->GetString());
mt = GetObjectMember(prv->jsonDoc, "params");
if(mt == nullptr || !mt->IsObject())
return set_socket_error("PARSE error: Protocol error 2");
opq_json_val v(mt);
return process_pool_job(&v);
}
else
{
uint64_t iCallId;
mt = GetObjectMember(prv->jsonDoc, "id");
if (mt == nullptr || !mt->IsUint64())
return set_socket_error("PARSE error: Protocol error 3");
iCallId = mt->GetUint64();
mt = GetObjectMember(prv->jsonDoc, "error");
const char* sError = nullptr;
size_t iErrorLen = 0;
if (mt == nullptr || mt->IsNull())
{
/* If there was no error we need a result */
if ((mt = GetObjectMember(prv->jsonDoc, "result")) == nullptr)
return set_socket_error("PARSE error: Protocol error 7");
}
else
{
if(!mt->IsObject())
return set_socket_error("PARSE error: Protocol error 5");
const Value* msg = GetObjectMember(*mt, "message");
if(msg == nullptr || !msg->IsString())
return set_socket_error("PARSE error: Protocol error 6");
iErrorLen = msg->GetStringLength();
sError = msg->GetString();
}
std::unique_lock mlock(call_mutex);
if (prv->oCallRsp.pCallData == nullptr)
{
/*Server sent us a call reply without us making a call*/
mlock.unlock();
return set_socket_error("PARSE error: Unexpected call response");
}
prv->oCallRsp.bHaveResponse = true;
prv->oCallRsp.iCallId = iCallId;
if(sError != nullptr)
{
prv->oCallRsp.pCallData = nullptr;
prv->oCallRsp.sCallErr.assign(sError, iErrorLen);
call_error = true;
}
else
prv->oCallRsp.pCallData->CopyFrom(*mt, prv->callAllocator);
mlock.unlock();
call_cond.notify_one();
return true;
}
}
bool jpsock::process_pool_job(const opq_json_val* params)
{
if (!params->val->IsObject())
return set_socket_error("PARSE error: Job error 1");
const Value *blob, *jobid, *target, *motd;
jobid = GetObjectMember(*params->val, "job_id");
blob = GetObjectMember(*params->val, "blob");
target = GetObjectMember(*params->val, "target");
motd = GetObjectMember(*params->val, "motd");
if (jobid == nullptr || blob == nullptr || target == nullptr ||
!jobid->IsString() || !blob->IsString() || !target->IsString())
{
return set_socket_error("PARSE error: Job error 2");
}
if(motd != nullptr && motd->IsString() && (motd->GetStringLength() & 0x01) == 0)
{
std::unique_lock lck(motd_mutex);
if(motd->GetStringLength() > 0)
{
pool_motd.resize(motd->GetStringLength()/2 + 1);
if(!hex2bin(motd->GetString(), motd->GetStringLength(), (unsigned char*)&pool_motd.front()))
pool_motd.clear();
}
else
pool_motd.clear();
}
if (jobid->GetStringLength() >= sizeof(pool_job::sJobID)) // Note >=
return set_socket_error("PARSE error: Job error 3");
pool_job oPoolJob;
const uint32_t iWorkLen = blob->GetStringLength() / 2;
oPoolJob.iWorkLen = iWorkLen;
if (iWorkLen > sizeof(pool_job::bWorkBlob))
return set_socket_error("PARSE error: Invalid job length. Are you sure you are mining the correct coin?");
if (!hex2bin(blob->GetString(), iWorkLen * 2, oPoolJob.bWorkBlob))
return set_socket_error("PARSE error: Job error 4");
// lock reading of oCurrentJob
std::unique_lock jobIdLock(job_mutex);
// compare possible non equal length job id's
if(iWorkLen == oCurrentJob.iWorkLen &&
memcmp(oPoolJob.bWorkBlob, oCurrentJob.bWorkBlob, iWorkLen) == 0 &&
strcmp(jobid->GetString(), oCurrentJob.sJobID) == 0
)
{
return set_socket_error("Duplicate equal job detected! Please contact your pool admin.");
}
jobIdLock.unlock();
memset(oPoolJob.sJobID, 0, sizeof(pool_job::sJobID));
memcpy(oPoolJob.sJobID, jobid->GetString(), jobid->GetStringLength()); //Bounds checking at proto error 3
size_t target_slen = target->GetStringLength();
if(target_slen <= 8)
{
uint32_t iTempInt = 0;
char sTempStr[] = "00000000"; // Little-endian CPU FTW
memcpy(sTempStr, target->GetString(), target_slen);
if(!hex2bin(sTempStr, 8, (unsigned char*)&iTempInt) || iTempInt == 0)
return set_socket_error("PARSE error: Invalid target");
oPoolJob.iTarget = t32_to_t64(iTempInt);
}
else if(target_slen <= 16)
{
oPoolJob.iTarget = 0;
char sTempStr[] = "0000000000000000";
memcpy(sTempStr, target->GetString(), target_slen);
if(!hex2bin(sTempStr, 16, (unsigned char*)&oPoolJob.iTarget) || oPoolJob.iTarget == 0)
return set_socket_error("PARSE error: Invalid target");
}
else
return set_socket_error("PARSE error: Job error 5");
iJobDiff = t64_to_diff(oPoolJob.iTarget);
std::unique_lock lck(job_mutex);
oCurrentJob = oPoolJob;
lck.unlock();
// send event after current job data are updated
executor::inst()->push_event(ex_event(oPoolJob, pool_id));
return true;
}
bool jpsock::connect(std::string& sConnectError)
{
ext_algo = ext_backend = ext_hashcount = ext_motd = false;
bHaveSocketError = false;
call_error = false;
sSocketError.clear();
iJobDiff = 0;
connect_attempts++;
connect_time = get_timestamp();
if(sck->set_hostname(net_addr.c_str()))
{
bRunning = true;
disconnect_time = 0;
oRecvThd = new std::thread(&jpsock::jpsock_thread, this);
return true;
}
disconnect_time = get_timestamp();
sConnectError = std::move(sSocketError);
return false;
}
void jpsock::disconnect(bool quiet)
{
quiet_close = quiet;
sck->close(false);
if(oRecvThd != nullptr)
{
oRecvThd->join();
delete oRecvThd;
oRecvThd = nullptr;
}
sck->close(true);
quiet_close = false;
}
bool jpsock::cmd_ret_wait(const char* sPacket, opq_json_val& poResult)
{
//printf("SEND: %s\n", sPacket);
/*Set up the call rsp for the call reply*/
prv->oCallValue.SetNull();
prv->callAllocator.Clear();
std::unique_lock mlock(call_mutex);
prv->oCallRsp = call_rsp(&prv->oCallValue);
mlock.unlock();
if(!sck->send(sPacket))
{
disconnect(); //This will join the other thread;
return false;
}
//Success is true if the server approves, result is true if there was no socket error
bool bSuccess;
mlock.lock();
bool bResult = call_cond.wait_for(mlock, std::chrono::seconds(jconf::inst()->GetCallTimeout()),
[&]() { return prv->oCallRsp.bHaveResponse; });
bSuccess = prv->oCallRsp.pCallData != nullptr;
prv->oCallRsp.pCallData = nullptr;
mlock.unlock();
if(bHaveSocketError)
return false;
//This means that there was no socket error, but the server is not taking to us
if(!bResult)
{
set_socket_error("CALL error: Timeout while waiting for a reply");
disconnect();
return false;
}
if(bSuccess)
poResult.val = &prv->oCallValue;
return bSuccess;
}
bool jpsock::cmd_login()
{
char cmd_buffer[1024];
snprintf(cmd_buffer, sizeof(cmd_buffer), "{\"method\":\"login\",\"params\":{\"login\":\"%s\",\"pass\":\"%s\",\"rigid\":\"%s\",\"agent\":\"%s\"},\"id\":1}\n",
usr_login.c_str(), usr_pass.c_str(), usr_rigid.c_str(), get_version_str().c_str());
opq_json_val oResult(nullptr);
/*Normal error conditions (failed login etc..) will end here*/
if (!cmd_ret_wait(cmd_buffer, oResult))
return false;
if (!oResult.val->IsObject())
{
set_socket_error("PARSE error: Login protocol error 1");
disconnect();
return false;
}
const Value* id = GetObjectMember(*oResult.val, "id");
const Value* job = GetObjectMember(*oResult.val, "job");
const Value* ext = GetObjectMember(*oResult.val, "extensions");
if (id == nullptr || job == nullptr || !id->IsString())
{
set_socket_error("PARSE error: Login protocol error 2");
disconnect();
return false;
}
if (id->GetStringLength() >= sizeof(sMinerId))
{
set_socket_error("PARSE error: Login protocol error 3");
disconnect();
return false;
}
memset(sMinerId, 0, sizeof(sMinerId));
memcpy(sMinerId, id->GetString(), id->GetStringLength());
if(ext != nullptr && ext->IsArray())
{
for(size_t i=0; i < ext->Size(); i++)
{
const Value& jextname = ext->GetArray()[i];
if(!jextname.IsString())
continue;
std::string tmp(jextname.GetString());
std::transform(tmp.begin(), tmp.end(), tmp.begin(), ::tolower);
if(tmp == "algo")
ext_algo = true;
else if(tmp == "backend")
ext_backend = true;
else if(tmp == "hashcount")
ext_hashcount = true;
else if(tmp == "motd")
ext_motd = true;
}
}
opq_json_val v(job);
if(!process_pool_job(&v))
{
disconnect();
return false;
}
bLoggedIn = true;
connect_attempts = 0;
return true;
}
bool jpsock::cmd_submit(const char* sJobId, uint32_t iNonce, const uint8_t* bResult, const char* backend_name, uint64_t backend_hashcount, uint64_t total_hashcount, xmrstak_algo algo)
{
char cmd_buffer[1024];
char sNonce[9];
char sResult[65];
/*Extensions*/
char sAlgo[64] = {0};
char sBackend[64] = {0};
char sHashcount[128] = {0};
if(ext_backend)
snprintf(sBackend, sizeof(sBackend), ",\"backend\":\"%s\"", backend_name);
if(ext_hashcount)
snprintf(sHashcount, sizeof(sHashcount), ",\"hashcount\":%llu,\"hashcount_total\":%llu", int_port(backend_hashcount), int_port(total_hashcount));
if(ext_algo)
{
const char* algo_name;
switch(algo)
{
case cryptonight:
algo_name = "cryptonight";
break;
case cryptonight_lite:
algo_name = "cryptonight_lite";
break;
case cryptonight_monero:
algo_name = "cryptonight_v7";
break;
case cryptonight_aeon:
algo_name = "cryptonight_lite_v7";
break;
case cryptonight_stellite:
algo_name = "cryptonight_v7_stellite";
break;
case cryptonight_ipbc:
algo_name = "cryptonight_lite_v7_xor";
break;
case cryptonight_heavy:
algo_name = "cryptonight_heavy";
break;
default:
algo_name = "unknown";
break;
}
snprintf(sAlgo, sizeof(sAlgo), ",\"algo\":\"%s\"", algo_name);
}
bin2hex((unsigned char*)&iNonce, 4, sNonce);
sNonce[8] = '\0';
bin2hex(bResult, 32, sResult);
sResult[64] = '\0';
snprintf(cmd_buffer, sizeof(cmd_buffer), "{\"method\":\"submit\",\"params\":{\"id\":\"%s\",\"job_id\":\"%s\",\"nonce\":\"%s\",\"result\":\"%s\"%s%s%s},\"id\":1}\n",
sMinerId, sJobId, sNonce, sResult, sBackend, sHashcount, sAlgo);
opq_json_val oResult(nullptr);
return cmd_ret_wait(cmd_buffer, oResult);
}
void jpsock::save_nonce(uint32_t nonce)
{
std::unique_lock lck(job_mutex);
oCurrentJob.iSavedNonce = nonce;
}
bool jpsock::get_current_job(pool_job& job)
{
std::unique_lock lck(job_mutex);
if(oCurrentJob.iWorkLen == 0)
return false;
job = oCurrentJob;
return true;
}
bool jpsock::get_pool_motd(std::string& strin)
{
if(!ext_motd)
return false;
std::unique_lock lck(motd_mutex);
if(pool_motd.size() > 0)
{
strin.assign(pool_motd);
return true;
}
return false;
}
inline unsigned char hf_hex2bin(char c, bool &err)
{
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'a' && c <= 'f')
return c - 'a' + 0xA;
else if (c >= 'A' && c <= 'F')
return c - 'A' + 0xA;
err = true;
return 0;
}
bool jpsock::hex2bin(const char* in, unsigned int len, unsigned char* out)
{
bool error = false;
for (unsigned int i = 0; i < len; i += 2)
{
out[i / 2] = (hf_hex2bin(in[i], error) << 4) | hf_hex2bin(in[i + 1], error);
if (error) return false;
}
return true;
}
inline char hf_bin2hex(unsigned char c)
{
if (c <= 0x9)
return '0' + c;
else
return 'a' - 0xA + c;
}
void jpsock::bin2hex(const unsigned char* in, unsigned int len, char* out)
{
for (unsigned int i = 0; i < len; i++)
{
out[i * 2] = hf_bin2hex((in[i] & 0xF0) >> 4);
out[i * 2 + 1] = hf_bin2hex(in[i] & 0x0F);
}
}