diff --git a/src/netbase.cpp b/src/netbase.cpp index 101df4dd8..392634120 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -1,783 +1,781 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #ifndef WIN32 #include #endif -#include // for to_lower() - #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL 0 #endif // Settings static proxyType proxyInfo[NET_MAX]; static proxyType nameProxy; static CCriticalSection cs_proxyInfos; int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; bool fNameLookup = DEFAULT_NAME_LOOKUP; // Need ample time for negotiation for very slow proxies such as Tor // (milliseconds) static const int SOCKS5_RECV_TIMEOUT = 20 * 1000; static std::atomic interruptSocks5Recv(false); enum Network ParseNetwork(std::string net) { - boost::to_lower(net); + Downcase(net); if (net == "ipv4") { return NET_IPV4; } if (net == "ipv6") { return NET_IPV6; } if (net == "onion") { return NET_ONION; } if (net == "tor") { LogPrintf("Warning: net name 'tor' is deprecated and will be removed " "in the future. You should use 'onion' instead.\n"); return NET_ONION; } return NET_UNROUTABLE; } std::string GetNetworkName(enum Network net) { switch (net) { case NET_IPV4: return "ipv4"; case NET_IPV6: return "ipv6"; case NET_ONION: return "onion"; default: return ""; } } static bool LookupIntern(const char *pszName, std::vector &vIP, unsigned int nMaxSolutions, bool fAllowLookup) { vIP.clear(); { CNetAddr addr; if (addr.SetSpecial(std::string(pszName))) { vIP.push_back(addr); return true; } } struct addrinfo aiHint; memset(&aiHint, 0, sizeof(struct addrinfo)); aiHint.ai_socktype = SOCK_STREAM; aiHint.ai_protocol = IPPROTO_TCP; aiHint.ai_family = AF_UNSPEC; #ifdef WIN32 aiHint.ai_flags = fAllowLookup ? 0 : AI_NUMERICHOST; #else aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST; #endif struct addrinfo *aiRes = nullptr; int nErr = getaddrinfo(pszName, nullptr, &aiHint, &aiRes); if (nErr) { return false; } struct addrinfo *aiTrav = aiRes; while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions)) { CNetAddr resolved; if (aiTrav->ai_family == AF_INET) { assert(aiTrav->ai_addrlen >= sizeof(sockaddr_in)); resolved = CNetAddr(reinterpret_cast(aiTrav->ai_addr) ->sin_addr); } if (aiTrav->ai_family == AF_INET6) { assert(aiTrav->ai_addrlen >= sizeof(sockaddr_in6)); struct sockaddr_in6 *s6 = reinterpret_cast(aiTrav->ai_addr); resolved = CNetAddr(s6->sin6_addr, s6->sin6_scope_id); } // Never allow resolving to an internal address. Consider any such // result invalid. if (!resolved.IsInternal()) { vIP.push_back(resolved); } aiTrav = aiTrav->ai_next; } freeaddrinfo(aiRes); return (vIP.size() > 0); } bool LookupHost(const char *pszName, std::vector &vIP, unsigned int nMaxSolutions, bool fAllowLookup) { std::string strHost(pszName); if (strHost.empty()) { return false; } if (strHost.front() == '[' && strHost.back() == ']') { strHost = strHost.substr(1, strHost.size() - 2); } return LookupIntern(strHost.c_str(), vIP, nMaxSolutions, fAllowLookup); } bool LookupHost(const char *pszName, CNetAddr &addr, bool fAllowLookup) { std::vector vIP; LookupHost(pszName, vIP, 1, fAllowLookup); if (vIP.empty()) { return false; } addr = vIP.front(); return true; } bool Lookup(const char *pszName, std::vector &vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions) { if (pszName[0] == 0) { return false; } int port = portDefault; std::string hostname = ""; SplitHostPort(std::string(pszName), port, hostname); std::vector vIP; bool fRet = LookupIntern(hostname.c_str(), vIP, nMaxSolutions, fAllowLookup); if (!fRet) { return false; } vAddr.resize(vIP.size()); for (unsigned int i = 0; i < vIP.size(); i++) { vAddr[i] = CService(vIP[i], port); } return true; } bool Lookup(const char *pszName, CService &addr, int portDefault, bool fAllowLookup) { std::vector vService; bool fRet = Lookup(pszName, vService, portDefault, fAllowLookup, 1); if (!fRet) { return false; } addr = vService[0]; return true; } CService LookupNumeric(const char *pszName, int portDefault) { CService addr; // "1.2:345" will fail to resolve the ip, but will still set the port. // If the ip fails to resolve, re-init the result. if (!Lookup(pszName, addr, portDefault, false)) { addr = CService(); } return addr; } struct timeval MillisToTimeval(int64_t nTimeout) { struct timeval timeout; timeout.tv_sec = nTimeout / 1000; timeout.tv_usec = (nTimeout % 1000) * 1000; return timeout; } /** SOCKS version */ enum SOCKSVersion : uint8_t { SOCKS4 = 0x04, SOCKS5 = 0x05 }; /** Values defined for METHOD in RFC1928 */ enum SOCKS5Method : uint8_t { NOAUTH = 0x00, //! No authentication required GSSAPI = 0x01, //! GSSAPI USER_PASS = 0x02, //! Username/password NO_ACCEPTABLE = 0xff, //! No acceptable methods }; /** Values defined for CMD in RFC1928 */ enum SOCKS5Command : uint8_t { CONNECT = 0x01, BIND = 0x02, UDP_ASSOCIATE = 0x03 }; /** Values defined for REP in RFC1928 */ enum SOCKS5Reply : uint8_t { SUCCEEDED = 0x00, //! Succeeded GENFAILURE = 0x01, //! General failure NOTALLOWED = 0x02, //! Connection not allowed by ruleset NETUNREACHABLE = 0x03, //! Network unreachable HOSTUNREACHABLE = 0x04, //! Network unreachable CONNREFUSED = 0x05, //! Connection refused TTLEXPIRED = 0x06, //! TTL expired CMDUNSUPPORTED = 0x07, //! Command not supported ATYPEUNSUPPORTED = 0x08, //! Address type not supported }; /** Values defined for ATYPE in RFC1928 */ enum SOCKS5Atyp : uint8_t { IPV4 = 0x01, DOMAINNAME = 0x03, IPV6 = 0x04, }; /** Status codes that can be returned by InterruptibleRecv */ enum class IntrRecvError { OK, Timeout, Disconnected, NetworkError, Interrupted }; /** * Read bytes from socket. This will either read the full number of bytes * requested or return False on error or timeout. * This function can be interrupted by calling InterruptSocks5() * * @param data Buffer to receive into * @param len Length of data to receive * @param timeout Timeout in milliseconds for receive operation * * @note This function requires that hSocket is in non-blocking mode. */ static IntrRecvError InterruptibleRecv(uint8_t *data, size_t len, int timeout, const SOCKET &hSocket) { int64_t curTime = GetTimeMillis(); int64_t endTime = curTime + timeout; // Maximum time to wait in one select call. It will take up until this time // (in millis) to break off in case of an interruption. const int64_t maxWait = 1000; while (len > 0 && curTime < endTime) { // Optimistically try the recv first ssize_t ret = recv(hSocket, (char *)data, len, 0); if (ret > 0) { len -= ret; data += ret; } else if (ret == 0) { // Unexpected disconnection return IntrRecvError::Disconnected; } else { // Other error or blocking int nErr = WSAGetLastError(); if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) { if (!IsSelectableSocket(hSocket)) { return IntrRecvError::NetworkError; } struct timeval tval = MillisToTimeval(std::min(endTime - curTime, maxWait)); fd_set fdset; FD_ZERO(&fdset); FD_SET(hSocket, &fdset); int nRet = select(hSocket + 1, &fdset, nullptr, nullptr, &tval); if (nRet == SOCKET_ERROR) { return IntrRecvError::NetworkError; } } else { return IntrRecvError::NetworkError; } } if (interruptSocks5Recv) { return IntrRecvError::Interrupted; } curTime = GetTimeMillis(); } return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout; } /** Credentials for proxy authentication */ struct ProxyCredentials { std::string username; std::string password; }; /** Convert SOCKS5 reply to an error message */ static std::string Socks5ErrorString(uint8_t err) { switch (err) { case SOCKS5Reply::GENFAILURE: return "general failure"; case SOCKS5Reply::NOTALLOWED: return "connection not allowed"; case SOCKS5Reply::NETUNREACHABLE: return "network unreachable"; case SOCKS5Reply::HOSTUNREACHABLE: return "host unreachable"; case SOCKS5Reply::CONNREFUSED: return "connection refused"; case SOCKS5Reply::TTLEXPIRED: return "TTL expired"; case SOCKS5Reply::CMDUNSUPPORTED: return "protocol error"; case SOCKS5Reply::ATYPEUNSUPPORTED: return "address type not supported"; default: return "unknown"; } } /** Connect using SOCKS5 (as described in RFC1928) */ static bool Socks5(const std::string &strDest, int port, const ProxyCredentials *auth, const SOCKET &hSocket) { IntrRecvError recvr; LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest); if (strDest.size() > 255) { return error("Hostname too long"); } // Accepted authentication methods std::vector vSocks5Init; vSocks5Init.push_back(SOCKSVersion::SOCKS5); if (auth) { vSocks5Init.push_back(0x02); // Number of methods vSocks5Init.push_back(SOCKS5Method::NOAUTH); vSocks5Init.push_back(SOCKS5Method::USER_PASS); } else { vSocks5Init.push_back(0x01); // Number of methods vSocks5Init.push_back(SOCKS5Method::NOAUTH); } ssize_t ret = send(hSocket, (const char *)vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL); if (ret != (ssize_t)vSocks5Init.size()) { return error("Error sending to proxy"); } uint8_t pchRet1[2]; if ((recvr = InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) { LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() " "timeout or other failure\n", strDest, port); return false; } if (pchRet1[0] != SOCKSVersion::SOCKS5) { return error("Proxy failed to initialize"); } if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) { // Perform username/password authentication (as described in RFC1929) std::vector vAuth; // Current (and only) version of user/pass subnegotiation vAuth.push_back(0x01); if (auth->username.size() > 255 || auth->password.size() > 255) { return error("Proxy username or password too long"); } vAuth.push_back(auth->username.size()); vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end()); vAuth.push_back(auth->password.size()); vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end()); ret = send(hSocket, (const char *)vAuth.data(), vAuth.size(), MSG_NOSIGNAL); if (ret != (ssize_t)vAuth.size()) { return error("Error sending authentication to proxy"); } LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password); uint8_t pchRetA[2]; if ((recvr = InterruptibleRecv(pchRetA, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) { return error("Error reading proxy authentication response"); } if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { return error("Proxy authentication unsuccessful"); } } else if (pchRet1[1] == SOCKS5Method::NOAUTH) { // Perform no authentication } else { return error("Proxy requested wrong authentication method %02x", pchRet1[1]); } std::vector vSocks5; // VER protocol version vSocks5.push_back(SOCKSVersion::SOCKS5); // CMD CONNECT vSocks5.push_back(SOCKS5Command::CONNECT); // RSV Reserved must be 0 vSocks5.push_back(0x00); // ATYP DOMAINNAME vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // Length<=255 is checked at beginning of function vSocks5.push_back(strDest.size()); vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end()); vSocks5.push_back((port >> 8) & 0xFF); vSocks5.push_back((port >> 0) & 0xFF); ret = send(hSocket, (const char *)vSocks5.data(), vSocks5.size(), MSG_NOSIGNAL); if (ret != (ssize_t)vSocks5.size()) { return error("Error sending to proxy"); } uint8_t pchRet2[4]; if ((recvr = InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) { if (recvr == IntrRecvError::Timeout) { /** * If a timeout happens here, this effectively means we timed out * while connecting to the remote node. This is very common for Tor, * so do not print an error message. */ return false; } else { return error("Error while reading proxy response"); } } if (pchRet2[0] != SOCKSVersion::SOCKS5) { return error("Proxy failed to accept request"); } if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) { // Failures to connect to a peer that are not proxy errors LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1])); return false; } // Reserved field must be 0 if (pchRet2[2] != 0x00) { return error("Error: malformed proxy response"); } uint8_t pchRet3[256]; switch (pchRet2[3]) { case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, hSocket); break; case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, hSocket); break; case SOCKS5Atyp::DOMAINNAME: { recvr = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, hSocket); if (recvr != IntrRecvError::OK) { return error("Error reading from proxy"); } int nRecv = pchRet3[0]; recvr = InterruptibleRecv(pchRet3, nRecv, SOCKS5_RECV_TIMEOUT, hSocket); break; } default: return error("Error: malformed proxy response"); } if (recvr != IntrRecvError::OK) { return error("Error reading from proxy"); } if ((recvr = InterruptibleRecv(pchRet3, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) { return error("Error reading from proxy"); } LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest); return true; } SOCKET CreateSocket(const CService &addrConnect) { struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (!addrConnect.GetSockAddr((struct sockaddr *)&sockaddr, &len)) { LogPrintf("Cannot create socket for %s: unsupported network\n", addrConnect.ToString()); return INVALID_SOCKET; } SOCKET hSocket = socket(((struct sockaddr *)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP); if (hSocket == INVALID_SOCKET) { return INVALID_SOCKET; } if (!IsSelectableSocket(hSocket)) { CloseSocket(hSocket); LogPrintf("Cannot create connection: non-selectable socket created (fd " ">= FD_SETSIZE ?)\n"); return INVALID_SOCKET; } #ifdef SO_NOSIGPIPE int set = 1; // Different way of disabling SIGPIPE on BSD setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (sockopt_arg_type)&set, sizeof(int)); #endif // Disable Nagle's algorithm SetSocketNoDelay(hSocket); // Set to non-blocking if (!SetSocketNonBlocking(hSocket, true)) { CloseSocket(hSocket); LogPrintf("ConnectSocketDirectly: Setting socket to non-blocking " "failed, error %s\n", NetworkErrorString(WSAGetLastError())); } return hSocket; } bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET &hSocket, int nTimeout) { struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (hSocket == INVALID_SOCKET) { LogPrintf("Cannot connect to %s: invalid socket\n", addrConnect.ToString()); return false; } if (!addrConnect.GetSockAddr((struct sockaddr *)&sockaddr, &len)) { LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToString()); return false; } if (connect(hSocket, (struct sockaddr *)&sockaddr, len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); // WSAEINVAL is here because some legacy version of winsock uses it if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) { struct timeval timeout = MillisToTimeval(nTimeout); fd_set fdset; FD_ZERO(&fdset); FD_SET(hSocket, &fdset); int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout); if (nRet == 0) { LogPrint(BCLog::NET, "connection to %s timeout\n", addrConnect.ToString()); return false; } if (nRet == SOCKET_ERROR) { LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); return false; } socklen_t nRetSize = sizeof(nRet); if (getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&nRet, &nRetSize) == SOCKET_ERROR) { LogPrintf("getsockopt() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); return false; } if (nRet != 0) { LogPrintf("connect() to %s failed after select(): %s\n", addrConnect.ToString(), NetworkErrorString(nRet)); return false; } } #ifdef WIN32 else if (WSAGetLastError() != WSAEISCONN) #else else #endif { LogPrintf("connect() to %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); return false; } } return true; } bool SetProxy(enum Network net, const proxyType &addrProxy) { assert(net >= 0 && net < NET_MAX); if (!addrProxy.IsValid()) { return false; } LOCK(cs_proxyInfos); proxyInfo[net] = addrProxy; return true; } bool GetProxy(enum Network net, proxyType &proxyInfoOut) { assert(net >= 0 && net < NET_MAX); LOCK(cs_proxyInfos); if (!proxyInfo[net].IsValid()) { return false; } proxyInfoOut = proxyInfo[net]; return true; } bool SetNameProxy(const proxyType &addrProxy) { if (!addrProxy.IsValid()) { return false; } LOCK(cs_proxyInfos); nameProxy = addrProxy; return true; } bool GetNameProxy(proxyType &nameProxyOut) { LOCK(cs_proxyInfos); if (!nameProxy.IsValid()) { return false; } nameProxyOut = nameProxy; return true; } bool HaveNameProxy() { LOCK(cs_proxyInfos); return nameProxy.IsValid(); } bool IsProxy(const CNetAddr &addr) { LOCK(cs_proxyInfos); for (int i = 0; i < NET_MAX; i++) { if (addr == static_cast(proxyInfo[i].proxy)) { return true; } } return false; } bool ConnectThroughProxy(const proxyType &proxy, const std::string &strDest, int port, const SOCKET &hSocket, int nTimeout, bool *outProxyConnectionFailed) { // first connect to proxy server if (!ConnectSocketDirectly(proxy.proxy, hSocket, nTimeout)) { if (outProxyConnectionFailed) { *outProxyConnectionFailed = true; } return false; } // do socks negotiation if (proxy.randomize_credentials) { ProxyCredentials random_auth; static std::atomic_int counter(0); random_auth.username = random_auth.password = strprintf("%i", counter++); if (!Socks5(strDest, (unsigned short)port, &random_auth, hSocket)) { return false; } } else if (!Socks5(strDest, (unsigned short)port, 0, hSocket)) { return false; } return true; } bool LookupSubNet(const char *pszName, CSubNet &ret) { std::string strSubnet(pszName); size_t slash = strSubnet.find_last_of('/'); std::vector vIP; std::string strAddress = strSubnet.substr(0, slash); if (LookupHost(strAddress.c_str(), vIP, 1, false)) { CNetAddr network = vIP[0]; if (slash != strSubnet.npos) { std::string strNetmask = strSubnet.substr(slash + 1); int32_t n; // IPv4 addresses start at offset 12, and first 12 bytes must match, // so just offset n if (ParseInt32(strNetmask, &n)) { // If valid number, assume /24 syntax ret = CSubNet(network, n); return ret.IsValid(); } else { // If not a valid number, try full netmask syntax // Never allow lookup for netmask if (LookupHost(strNetmask.c_str(), vIP, 1, false)) { ret = CSubNet(network, vIP[0]); return ret.IsValid(); } } } else { ret = CSubNet(network); return ret.IsValid(); } } return false; } #ifdef WIN32 std::string NetworkErrorString(int err) { char buf[256]; buf[0] = 0; if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, sizeof(buf), nullptr)) { return strprintf("%s (%d)", buf, err); } else { return strprintf("Unknown error (%d)", err); } } #else std::string NetworkErrorString(int err) { char buf[256]; const char *s = buf; buf[0] = 0; /* Too bad there are two incompatible implementations of the * thread-safe strerror. */ #ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */ s = strerror_r(err, buf, sizeof(buf)); #else /* POSIX variant always returns message in buffer */ if (strerror_r(err, buf, sizeof(buf))) { buf[0] = 0; } #endif return strprintf("%s (%d)", s, err); } #endif bool CloseSocket(SOCKET &hSocket) { if (hSocket == INVALID_SOCKET) { return false; } #ifdef WIN32 int ret = closesocket(hSocket); #else int ret = close(hSocket); #endif hSocket = INVALID_SOCKET; return ret != SOCKET_ERROR; } bool SetSocketNonBlocking(const SOCKET &hSocket, bool fNonBlocking) { if (fNonBlocking) { #ifdef WIN32 u_long nOne = 1; if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) { #else int fFlags = fcntl(hSocket, F_GETFL, 0); if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == SOCKET_ERROR) { #endif return false; } } else { #ifdef WIN32 u_long nZero = 0; if (ioctlsocket(hSocket, FIONBIO, &nZero) == SOCKET_ERROR) { #else int fFlags = fcntl(hSocket, F_GETFL, 0); if (fcntl(hSocket, F_SETFL, fFlags & ~O_NONBLOCK) == SOCKET_ERROR) { #endif return false; } } return true; } bool SetSocketNoDelay(const SOCKET &hSocket) { int set = 1; int rc = setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (sockopt_arg_type)&set, sizeof(int)); return rc == 0; } void InterruptSocks5(bool interrupt) { interruptSocks5Recv = interrupt; } diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 0cc845878..45a042d44 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -1,609 +1,605 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Copyright (c) 2018-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "rpc/server.h" #include "base58.h" #include "config.h" #include "fs.h" #include "init.h" #include "random.h" #include "sync.h" #include "ui_interface.h" #include "util.h" #include "utilstrencodings.h" #include -#include // for to_upper() #include #include #include #include #include // for unique_ptr #include #include static bool fRPCRunning = false; static bool fRPCInWarmup = true; static std::string rpcWarmupStatus("RPC server started"); static CCriticalSection cs_rpcWarmup; /* Timer-creating functions */ static RPCTimerInterface *timerInterface = nullptr; /* Map of name to timer. */ static std::map> deadlineTimers; UniValue RPCServer::ExecuteCommand(Config &config, const JSONRPCRequest &request) const { // Return immediately if in warmup // This is retained from the old RPC implementation because a lot of state // is set during warmup that RPC commands may depend on. This can be // safely removed once global variable usage has been eliminated. { LOCK(cs_rpcWarmup); if (fRPCInWarmup) { throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); } } std::string commandName = request.strMethod; { auto commandsReadView = commands.getReadView(); auto iter = commandsReadView->find(commandName); if (iter != commandsReadView.end()) { return iter->second.get()->Execute(request); } } // TODO Remove the below call to tableRPC.execute() and only call it for // context-free RPC commands via an implementation of RPCCommand. // Check if context-free RPC method is valid and execute it return tableRPC.execute(config, request); } void RPCServer::RegisterCommand(std::unique_ptr command) { if (command != nullptr) { const std::string &commandName = command->GetName(); commands.getWriteView()->insert( std::make_pair(commandName, std::move(command))); } } static struct CRPCSignals { boost::signals2::signal Started; boost::signals2::signal Stopped; boost::signals2::signal PreCommand; } g_rpcSignals; void RPCServerSignals::OnStarted(std::function slot) { g_rpcSignals.Started.connect(slot); } void RPCServerSignals::OnStopped(std::function slot) { g_rpcSignals.Stopped.connect(slot); } void RPCTypeCheck(const UniValue ¶ms, const std::list &typesExpected, bool fAllowNull) { unsigned int i = 0; for (UniValue::VType t : typesExpected) { if (params.size() <= i) { break; } const UniValue &v = params[i]; if (!(fAllowNull && v.isNull())) { RPCTypeCheckArgument(v, t); } i++; } } void RPCTypeCheckArgument(const UniValue &value, UniValue::VType typeExpected) { if (value.type() != typeExpected) { throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected type %s, got %s", uvTypeName(typeExpected), uvTypeName(value.type()))); } } void RPCTypeCheckObj(const UniValue &o, const std::map &typesExpected, bool fAllowNull, bool fStrict) { for (const auto &t : typesExpected) { const UniValue &v = find_value(o, t.first); if (!fAllowNull && v.isNull()) { throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first)); } if (!(t.second.typeAny || v.type() == t.second.type || (fAllowNull && v.isNull()))) { std::string err = strprintf("Expected type %s for %s, got %s", uvTypeName(t.second.type), t.first, uvTypeName(v.type())); throw JSONRPCError(RPC_TYPE_ERROR, err); } } if (fStrict) { for (const std::string &k : o.getKeys()) { if (typesExpected.count(k) == 0) { std::string err = strprintf("Unexpected key %s", k); throw JSONRPCError(RPC_TYPE_ERROR, err); } } } } Amount AmountFromValue(const UniValue &value) { if (!value.isNum() && !value.isStr()) { throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number or string"); } int64_t n; if (!ParseFixedPoint(value.getValStr(), 8, &n)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); } Amount amt = n * SATOSHI; if (!MoneyRange(amt)) { throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range"); } return amt; } UniValue ValueFromAmount(const Amount amount) { bool sign = amount < Amount::zero(); Amount n_abs(sign ? -amount : amount); int64_t quotient = n_abs / COIN; int64_t remainder = (n_abs % COIN) / SATOSHI; return UniValue(UniValue::VNUM, strprintf("%s%d.%08d", sign ? "-" : "", quotient, remainder)); } uint256 ParseHashV(const UniValue &v, std::string strName) { std::string strHex; if (v.isStr()) { strHex = v.get_str(); } // Note: IsHex("") is false if (!IsHex(strHex)) { throw JSONRPCError(RPC_INVALID_PARAMETER, strName + " must be hexadecimal string (not '" + strHex + "')"); } if (strHex.length() != 64) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be of length %d (not %d)", strName, 64, strHex.length())); } uint256 result; result.SetHex(strHex); return result; } uint256 ParseHashO(const UniValue &o, std::string strKey) { return ParseHashV(find_value(o, strKey), strKey); } std::vector ParseHexV(const UniValue &v, std::string strName) { std::string strHex; if (v.isStr()) { strHex = v.get_str(); } if (!IsHex(strHex)) { throw JSONRPCError(RPC_INVALID_PARAMETER, strName + " must be hexadecimal string (not '" + strHex + "')"); } return ParseHex(strHex); } std::vector ParseHexO(const UniValue &o, std::string strKey) { return ParseHexV(find_value(o, strKey), strKey); } /** * Note: This interface may still be subject to change. */ std::string CRPCTable::help(Config &config, const std::string &strCommand, const JSONRPCRequest &helpreq) const { std::string strRet; std::string category; std::set setDone; std::vector> vCommands; for (const auto &entry : mapCommands) { vCommands.push_back( std::make_pair(entry.second->category + entry.first, entry.second)); } sort(vCommands.begin(), vCommands.end()); JSONRPCRequest jreq(helpreq); jreq.fHelp = true; jreq.params = UniValue(); for (const std::pair &command : vCommands) { const ContextFreeRPCCommand *pcmd = command.second; std::string strMethod = pcmd->name; // We already filter duplicates, but these deprecated screw up the sort // order if (strMethod.find("label") != std::string::npos) { continue; } if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand) { continue; } jreq.strMethod = strMethod; try { if (setDone.insert(pcmd).second) { pcmd->call(config, jreq); } } catch (const std::exception &e) { // Help text is returned in an exception std::string strHelp = std::string(e.what()); if (strCommand == "") { if (strHelp.find('\n') != std::string::npos) { strHelp = strHelp.substr(0, strHelp.find('\n')); } if (category != pcmd->category) { if (!category.empty()) { strRet += "\n"; } category = pcmd->category; - std::string firstLetter = category.substr(0, 1); - boost::to_upper(firstLetter); - strRet += - "== " + firstLetter + category.substr(1) + " ==\n"; + strRet += "== " + Capitalize(category) + " ==\n"; } } strRet += strHelp + "\n"; } } if (strRet == "") { strRet = strprintf("help: unknown command: %s\n", strCommand); } strRet = strRet.substr(0, strRet.size() - 1); return strRet; } static UniValue help(Config &config, const JSONRPCRequest &jsonRequest) { if (jsonRequest.fHelp || jsonRequest.params.size() > 1) { throw std::runtime_error( "help ( \"command\" )\n" "\nList all commands, or get help for a specified command.\n" "\nArguments:\n" "1. \"command\" (string, optional) The command to get help on\n" "\nResult:\n" "\"text\" (string) The help text\n"); } std::string strCommand; if (jsonRequest.params.size() > 0) { strCommand = jsonRequest.params[0].get_str(); } return tableRPC.help(config, strCommand, jsonRequest); } static UniValue stop(const Config &config, const JSONRPCRequest &jsonRequest) { // Accept the deprecated and ignored 'detach' boolean argument if (jsonRequest.fHelp || jsonRequest.params.size() > 1) { throw std::runtime_error("stop\n" "\nStop Bitcoin server."); } // Event loop will exit after current HTTP requests have been handled, so // this reply will get back to the client. StartShutdown(); return "Bitcoin server stopping"; } static UniValue uptime(const Config &config, const JSONRPCRequest &jsonRequest) { if (jsonRequest.fHelp || jsonRequest.params.size() > 1) { throw std::runtime_error("uptime\n" "\nReturns the total uptime of the server.\n" "\nResult:\n" "ttt (numeric) The number of seconds " "that the server has been running\n" "\nExamples:\n" + HelpExampleCli("uptime", "") + HelpExampleRpc("uptime", "")); } return GetTime() - GetStartupTime(); } /** * Call Table */ // clang-format off static const ContextFreeRPCCommand vRPCCommands[] = { // category name actor (function) argNames // ------------------- ------------------------ ---------------------- ---------- /* Overall control/query calls */ { "control", "help", help, {"command"} }, { "control", "stop", stop, {} }, { "control", "uptime", uptime, {} }, }; // clang-format on CRPCTable::CRPCTable() { unsigned int vcidx; for (vcidx = 0; vcidx < (sizeof(vRPCCommands) / sizeof(vRPCCommands[0])); vcidx++) { const ContextFreeRPCCommand *pcmd; pcmd = &vRPCCommands[vcidx]; mapCommands[pcmd->name] = pcmd; } } const ContextFreeRPCCommand *CRPCTable:: operator[](const std::string &name) const { std::map::const_iterator it = mapCommands.find(name); if (it == mapCommands.end()) { return nullptr; } return (*it).second; } bool CRPCTable::appendCommand(const std::string &name, const ContextFreeRPCCommand *pcmd) { if (IsRPCRunning()) { return false; } // don't allow overwriting for now std::map::const_iterator it = mapCommands.find(name); if (it != mapCommands.end()) { return false; } mapCommands[name] = pcmd; return true; } bool StartRPC() { LogPrint(BCLog::RPC, "Starting RPC\n"); fRPCRunning = true; g_rpcSignals.Started(); return true; } void InterruptRPC() { LogPrint(BCLog::RPC, "Interrupting RPC\n"); // Interrupt e.g. running longpolls fRPCRunning = false; } void StopRPC() { LogPrint(BCLog::RPC, "Stopping RPC\n"); deadlineTimers.clear(); DeleteAuthCookie(); g_rpcSignals.Stopped(); } bool IsRPCRunning() { return fRPCRunning; } void SetRPCWarmupStatus(const std::string &newStatus) { LOCK(cs_rpcWarmup); rpcWarmupStatus = newStatus; } void SetRPCWarmupFinished() { LOCK(cs_rpcWarmup); assert(fRPCInWarmup); fRPCInWarmup = false; } bool RPCIsInWarmup(std::string *outStatus) { LOCK(cs_rpcWarmup); if (outStatus) { *outStatus = rpcWarmupStatus; } return fRPCInWarmup; } bool IsDeprecatedRPCEnabled(ArgsManager &args, const std::string &method) { const std::vector enabled_methods = args.GetArgs("-deprecatedrpc"); return find(enabled_methods.begin(), enabled_methods.end(), method) != enabled_methods.end(); } static UniValue JSONRPCExecOne(Config &config, RPCServer &rpcServer, JSONRPCRequest jreq, const UniValue &req) { UniValue rpc_result(UniValue::VOBJ); try { jreq.parse(req); UniValue result = rpcServer.ExecuteCommand(config, jreq); rpc_result = JSONRPCReplyObj(result, NullUniValue, jreq.id); } catch (const UniValue &objError) { rpc_result = JSONRPCReplyObj(NullUniValue, objError, jreq.id); } catch (const std::exception &e) { rpc_result = JSONRPCReplyObj( NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); } return rpc_result; } std::string JSONRPCExecBatch(Config &config, RPCServer &rpcServer, const JSONRPCRequest &jreq, const UniValue &vReq) { UniValue ret(UniValue::VARR); for (size_t i = 0; i < vReq.size(); i++) { ret.push_back(JSONRPCExecOne(config, rpcServer, jreq, vReq[i])); } return ret.write() + "\n"; } /** * Process named arguments into a vector of positional arguments, based on the * passed-in specification for the RPC call's arguments. */ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest &in, const std::vector &argNames) { JSONRPCRequest out = in; out.params = UniValue(UniValue::VARR); // Build a map of parameters, and remove ones that have been processed, so // that we can throw a focused error if there is an unknown one. const std::vector &keys = in.params.getKeys(); const std::vector &values = in.params.getValues(); std::unordered_map argsIn; for (size_t i = 0; i < keys.size(); ++i) { argsIn[keys[i]] = &values[i]; } // Process expected parameters. int hole = 0; for (const std::string &argNamePattern : argNames) { std::vector vargNames; boost::algorithm::split(vargNames, argNamePattern, boost::algorithm::is_any_of("|")); auto fr = argsIn.end(); for (const std::string &argName : vargNames) { fr = argsIn.find(argName); if (fr != argsIn.end()) { break; } } if (fr != argsIn.end()) { for (int i = 0; i < hole; ++i) { // Fill hole between specified parameters with JSON nulls, but // not at the end (for backwards compatibility with calls that // act based on number of specified parameters). out.params.push_back(UniValue()); } hole = 0; out.params.push_back(*fr->second); argsIn.erase(fr); } else { hole += 1; } } // If there are still arguments in the argsIn map, this is an error. if (!argsIn.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown named parameter " + argsIn.begin()->first); } // Return request with named arguments transformed to positional arguments return out; } UniValue CRPCTable::execute(Config &config, const JSONRPCRequest &request) const { // Return immediately if in warmup { LOCK(cs_rpcWarmup); if (fRPCInWarmup) { throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); } } // Check if legacy RPC method is valid. // See RPCServer::ExecuteCommand for context-sensitive RPC commands. const ContextFreeRPCCommand *pcmd = tableRPC[request.strMethod]; if (!pcmd) { throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); } g_rpcSignals.PreCommand(*pcmd); try { // Execute, convert arguments to array if necessary if (request.params.isObject()) { return pcmd->call(config, transformNamedArguments(request, pcmd->argNames)); } else { return pcmd->call(config, request); } } catch (const std::exception &e) { throw JSONRPCError(RPC_MISC_ERROR, e.what()); } } std::vector CRPCTable::listCommands() const { std::vector commandList; typedef std::map commandMap; std::transform(mapCommands.begin(), mapCommands.end(), std::back_inserter(commandList), boost::bind(&commandMap::value_type::first, _1)); return commandList; } std::string HelpExampleCli(const std::string &methodname, const std::string &args) { return "> bitcoin-cli " + methodname + " " + args + "\n"; } std::string HelpExampleRpc(const std::string &methodname, const std::string &args) { return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", " "\"id\":\"curltest\", " "\"method\": \"" + methodname + "\", \"params\": [" + args + "] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"; } void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface) { if (!timerInterface) { timerInterface = iface; } } void RPCSetTimerInterface(RPCTimerInterface *iface) { timerInterface = iface; } void RPCUnsetTimerInterface(RPCTimerInterface *iface) { if (timerInterface == iface) { timerInterface = nullptr; } } void RPCRunLater(const std::string &name, std::function func, int64_t nSeconds) { if (!timerInterface) { throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC"); } deadlineTimers.erase(name); LogPrint(BCLog::RPC, "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name()); deadlineTimers.emplace( name, std::unique_ptr( timerInterface->NewTimer(func, nSeconds * 1000))); } int RPCSerializationFlags() { return 0; } CRPCTable tableRPC; diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index d5736080a..be730af8f 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -1,337 +1,354 @@ // Copyright (c) 2012-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "netbase.h" #include "test/test_bitcoin.h" #include "utilstrencodings.h" #include #include BOOST_FIXTURE_TEST_SUITE(netbase_tests, BasicTestingSetup) static CNetAddr ResolveIP(const char *ip) { CNetAddr addr; LookupHost(ip, addr, false); return addr; } static CSubNet ResolveSubNet(const char *subnet) { CSubNet ret; LookupSubNet(subnet, ret); return ret; } static CNetAddr CreateInternal(const char *host) { CNetAddr addr; addr.SetInternal(host); return addr; } BOOST_AUTO_TEST_CASE(netbase_networks) { BOOST_CHECK(ResolveIP("127.0.0.1").GetNetwork() == NET_UNROUTABLE); BOOST_CHECK(ResolveIP("::1").GetNetwork() == NET_UNROUTABLE); BOOST_CHECK(ResolveIP("8.8.8.8").GetNetwork() == NET_IPV4); BOOST_CHECK(ResolveIP("2001::8888").GetNetwork() == NET_IPV6); BOOST_CHECK( ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetNetwork() == NET_ONION); BOOST_CHECK(CreateInternal("foo.com").GetNetwork() == NET_INTERNAL); } BOOST_AUTO_TEST_CASE(netbase_properties) { BOOST_CHECK(ResolveIP("127.0.0.1").IsIPv4()); BOOST_CHECK(ResolveIP("::FFFF:192.168.1.1").IsIPv4()); BOOST_CHECK(ResolveIP("::1").IsIPv6()); BOOST_CHECK(ResolveIP("10.0.0.1").IsRFC1918()); BOOST_CHECK(ResolveIP("192.168.1.1").IsRFC1918()); BOOST_CHECK(ResolveIP("172.31.255.255").IsRFC1918()); BOOST_CHECK(ResolveIP("2001:0DB8::").IsRFC3849()); BOOST_CHECK(ResolveIP("169.254.1.1").IsRFC3927()); BOOST_CHECK(ResolveIP("2002::1").IsRFC3964()); BOOST_CHECK(ResolveIP("FC00::").IsRFC4193()); BOOST_CHECK(ResolveIP("2001::2").IsRFC4380()); BOOST_CHECK(ResolveIP("2001:10::").IsRFC4843()); BOOST_CHECK(ResolveIP("FE80::").IsRFC4862()); BOOST_CHECK(ResolveIP("64:FF9B::").IsRFC6052()); BOOST_CHECK(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").IsTor()); BOOST_CHECK(ResolveIP("127.0.0.1").IsLocal()); BOOST_CHECK(ResolveIP("::1").IsLocal()); BOOST_CHECK(ResolveIP("8.8.8.8").IsRoutable()); BOOST_CHECK(ResolveIP("2001::1").IsRoutable()); BOOST_CHECK(ResolveIP("127.0.0.1").IsValid()); BOOST_CHECK( CreateInternal("FD6B:88C0:8724:edb1:8e4:3588:e546:35ca").IsInternal()); BOOST_CHECK(CreateInternal("bar.com").IsInternal()); } static bool TestSplitHost(std::string test, std::string host, int port) { std::string hostOut; int portOut = -1; SplitHostPort(test, portOut, hostOut); return hostOut == host && port == portOut; } BOOST_AUTO_TEST_CASE(netbase_splithost) { BOOST_CHECK(TestSplitHost("www.bitcoin.org", "www.bitcoin.org", -1)); BOOST_CHECK(TestSplitHost("[www.bitcoin.org]", "www.bitcoin.org", -1)); BOOST_CHECK(TestSplitHost("www.bitcoin.org:80", "www.bitcoin.org", 80)); BOOST_CHECK(TestSplitHost("[www.bitcoin.org]:80", "www.bitcoin.org", 80)); BOOST_CHECK(TestSplitHost("127.0.0.1", "127.0.0.1", -1)); BOOST_CHECK(TestSplitHost("127.0.0.1:8333", "127.0.0.1", 8333)); BOOST_CHECK(TestSplitHost("[127.0.0.1]", "127.0.0.1", -1)); BOOST_CHECK(TestSplitHost("[127.0.0.1]:8333", "127.0.0.1", 8333)); BOOST_CHECK(TestSplitHost("::ffff:127.0.0.1", "::ffff:127.0.0.1", -1)); BOOST_CHECK( TestSplitHost("[::ffff:127.0.0.1]:8333", "::ffff:127.0.0.1", 8333)); BOOST_CHECK(TestSplitHost("[::]:8333", "::", 8333)); BOOST_CHECK(TestSplitHost("::8333", "::8333", -1)); BOOST_CHECK(TestSplitHost(":8333", "", 8333)); BOOST_CHECK(TestSplitHost("[]:8333", "", 8333)); BOOST_CHECK(TestSplitHost("", "", -1)); } static bool TestParse(std::string src, std::string canon) { CService addr(LookupNumeric(src.c_str(), 65535)); return canon == addr.ToString(); } BOOST_AUTO_TEST_CASE(netbase_lookupnumeric) { BOOST_CHECK(TestParse("127.0.0.1", "127.0.0.1:65535")); BOOST_CHECK(TestParse("127.0.0.1:8333", "127.0.0.1:8333")); BOOST_CHECK(TestParse("::ffff:127.0.0.1", "127.0.0.1:65535")); BOOST_CHECK(TestParse("::", "[::]:65535")); BOOST_CHECK(TestParse("[::]:8333", "[::]:8333")); BOOST_CHECK(TestParse("[127.0.0.1]", "127.0.0.1:65535")); BOOST_CHECK(TestParse(":::", "[::]:0")); // verify that an internal address fails to resolve BOOST_CHECK(TestParse("[fd6b:88c0:8724:1:2:3:4:5]", "[::]:0")); // and that a one-off resolves correctly BOOST_CHECK(TestParse("[fd6c:88c0:8724:1:2:3:4:5]", "[fd6c:88c0:8724:1:2:3:4:5]:65535")); } BOOST_AUTO_TEST_CASE(onioncat_test) { // values from // https://web.archive.org/web/20121122003543/http://www.cypherpunk.at/onioncat/wiki/OnionCat CNetAddr addr1(ResolveIP("5wyqrzbvrdsumnok.onion")); CNetAddr addr2(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca")); BOOST_CHECK(addr1 == addr2); BOOST_CHECK(addr1.IsTor()); BOOST_CHECK(addr1.ToStringIP() == "5wyqrzbvrdsumnok.onion"); BOOST_CHECK(addr1.IsRoutable()); } BOOST_AUTO_TEST_CASE(subnet_test) { BOOST_CHECK(ResolveSubNet("1.2.3.0/24") == ResolveSubNet("1.2.3.0/255.255.255.0")); BOOST_CHECK(ResolveSubNet("1.2.3.0/24") != ResolveSubNet("1.2.4.0/255.255.255.0")); BOOST_CHECK(ResolveSubNet("1.2.3.0/24").Match(ResolveIP("1.2.3.4"))); BOOST_CHECK(!ResolveSubNet("1.2.2.0/24").Match(ResolveIP("1.2.3.4"))); BOOST_CHECK(ResolveSubNet("1.2.3.4").Match(ResolveIP("1.2.3.4"))); BOOST_CHECK(ResolveSubNet("1.2.3.4/32").Match(ResolveIP("1.2.3.4"))); BOOST_CHECK(!ResolveSubNet("1.2.3.4").Match(ResolveIP("5.6.7.8"))); BOOST_CHECK(!ResolveSubNet("1.2.3.4/32").Match(ResolveIP("5.6.7.8"))); BOOST_CHECK( ResolveSubNet("::ffff:127.0.0.1").Match(ResolveIP("127.0.0.1"))); BOOST_CHECK( ResolveSubNet("1:2:3:4:5:6:7:8").Match(ResolveIP("1:2:3:4:5:6:7:8"))); BOOST_CHECK( !ResolveSubNet("1:2:3:4:5:6:7:8").Match(ResolveIP("1:2:3:4:5:6:7:9"))); BOOST_CHECK(ResolveSubNet("1:2:3:4:5:6:7:0/112") .Match(ResolveIP("1:2:3:4:5:6:7:1234"))); BOOST_CHECK( ResolveSubNet("192.168.0.1/24").Match(ResolveIP("192.168.0.2"))); BOOST_CHECK( ResolveSubNet("192.168.0.20/29").Match(ResolveIP("192.168.0.18"))); BOOST_CHECK(ResolveSubNet("1.2.2.1/24").Match(ResolveIP("1.2.2.4"))); BOOST_CHECK(ResolveSubNet("1.2.2.110/31").Match(ResolveIP("1.2.2.111"))); BOOST_CHECK(ResolveSubNet("1.2.2.20/26").Match(ResolveIP("1.2.2.63"))); // All-Matching IPv6 Matches arbitrary IPv4 and IPv6 BOOST_CHECK(ResolveSubNet("::/0").Match(ResolveIP("1:2:3:4:5:6:7:1234"))); BOOST_CHECK(ResolveSubNet("::/0").Match(ResolveIP("1.2.3.4"))); // All-Matching IPv4 does not Match IPv6 BOOST_CHECK( !ResolveSubNet("0.0.0.0/0").Match(ResolveIP("1:2:3:4:5:6:7:1234"))); // Invalid subnets Match nothing (not even invalid addresses) BOOST_CHECK(!CSubNet().Match(ResolveIP("1.2.3.4"))); BOOST_CHECK(!ResolveSubNet("").Match(ResolveIP("4.5.6.7"))); BOOST_CHECK(!ResolveSubNet("bloop").Match(ResolveIP("0.0.0.0"))); BOOST_CHECK(!ResolveSubNet("bloop").Match(ResolveIP("hab"))); // Check valid/invalid BOOST_CHECK(ResolveSubNet("1.2.3.0/0").IsValid()); BOOST_CHECK(!ResolveSubNet("1.2.3.0/-1").IsValid()); BOOST_CHECK(ResolveSubNet("1.2.3.0/32").IsValid()); BOOST_CHECK(!ResolveSubNet("1.2.3.0/33").IsValid()); BOOST_CHECK(ResolveSubNet("1:2:3:4:5:6:7:8/0").IsValid()); BOOST_CHECK(ResolveSubNet("1:2:3:4:5:6:7:8/33").IsValid()); BOOST_CHECK(!ResolveSubNet("1:2:3:4:5:6:7:8/-1").IsValid()); BOOST_CHECK(ResolveSubNet("1:2:3:4:5:6:7:8/128").IsValid()); BOOST_CHECK(!ResolveSubNet("1:2:3:4:5:6:7:8/129").IsValid()); BOOST_CHECK(!ResolveSubNet("fuzzy").IsValid()); // CNetAddr constructor test BOOST_CHECK(CSubNet(ResolveIP("127.0.0.1")).IsValid()); BOOST_CHECK(CSubNet(ResolveIP("127.0.0.1")).Match(ResolveIP("127.0.0.1"))); BOOST_CHECK(!CSubNet(ResolveIP("127.0.0.1")).Match(ResolveIP("127.0.0.2"))); BOOST_CHECK(CSubNet(ResolveIP("127.0.0.1")).ToString() == "127.0.0.1/32"); CSubNet subnet = CSubNet(ResolveIP("1.2.3.4"), 32); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.4/32"); subnet = CSubNet(ResolveIP("1.2.3.4"), 8); BOOST_CHECK_EQUAL(subnet.ToString(), "1.0.0.0/8"); subnet = CSubNet(ResolveIP("1.2.3.4"), 0); BOOST_CHECK_EQUAL(subnet.ToString(), "0.0.0.0/0"); subnet = CSubNet(ResolveIP("1.2.3.4"), ResolveIP("255.255.255.255")); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.4/32"); subnet = CSubNet(ResolveIP("1.2.3.4"), ResolveIP("255.0.0.0")); BOOST_CHECK_EQUAL(subnet.ToString(), "1.0.0.0/8"); subnet = CSubNet(ResolveIP("1.2.3.4"), ResolveIP("0.0.0.0")); BOOST_CHECK_EQUAL(subnet.ToString(), "0.0.0.0/0"); BOOST_CHECK(CSubNet(ResolveIP("1:2:3:4:5:6:7:8")).IsValid()); BOOST_CHECK(CSubNet(ResolveIP("1:2:3:4:5:6:7:8")) .Match(ResolveIP("1:2:3:4:5:6:7:8"))); BOOST_CHECK(!CSubNet(ResolveIP("1:2:3:4:5:6:7:8")) .Match(ResolveIP("1:2:3:4:5:6:7:9"))); BOOST_CHECK(CSubNet(ResolveIP("1:2:3:4:5:6:7:8")).ToString() == "1:2:3:4:5:6:7:8/128"); subnet = ResolveSubNet("1.2.3.4/255.255.255.255"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.4/32"); subnet = ResolveSubNet("1.2.3.4/255.255.255.254"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.4/31"); subnet = ResolveSubNet("1.2.3.4/255.255.255.252"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.4/30"); subnet = ResolveSubNet("1.2.3.4/255.255.255.248"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.0/29"); subnet = ResolveSubNet("1.2.3.4/255.255.255.240"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.0/28"); subnet = ResolveSubNet("1.2.3.4/255.255.255.224"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.0/27"); subnet = ResolveSubNet("1.2.3.4/255.255.255.192"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.0/26"); subnet = ResolveSubNet("1.2.3.4/255.255.255.128"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.0/25"); subnet = ResolveSubNet("1.2.3.4/255.255.255.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.0/24"); subnet = ResolveSubNet("1.2.3.4/255.255.254.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.2.0/23"); subnet = ResolveSubNet("1.2.3.4/255.255.252.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/22"); subnet = ResolveSubNet("1.2.3.4/255.255.248.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/21"); subnet = ResolveSubNet("1.2.3.4/255.255.240.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/20"); subnet = ResolveSubNet("1.2.3.4/255.255.224.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/19"); subnet = ResolveSubNet("1.2.3.4/255.255.192.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/18"); subnet = ResolveSubNet("1.2.3.4/255.255.128.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/17"); subnet = ResolveSubNet("1.2.3.4/255.255.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/16"); subnet = ResolveSubNet("1.2.3.4/255.254.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/15"); subnet = ResolveSubNet("1.2.3.4/255.252.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.0.0.0/14"); subnet = ResolveSubNet("1.2.3.4/255.248.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.0.0.0/13"); subnet = ResolveSubNet("1.2.3.4/255.240.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.0.0.0/12"); subnet = ResolveSubNet("1.2.3.4/255.224.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.0.0.0/11"); subnet = ResolveSubNet("1.2.3.4/255.192.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.0.0.0/10"); subnet = ResolveSubNet("1.2.3.4/255.128.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.0.0.0/9"); subnet = ResolveSubNet("1.2.3.4/255.0.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.0.0.0/8"); subnet = ResolveSubNet("1.2.3.4/254.0.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "0.0.0.0/7"); subnet = ResolveSubNet("1.2.3.4/252.0.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "0.0.0.0/6"); subnet = ResolveSubNet("1.2.3.4/248.0.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "0.0.0.0/5"); subnet = ResolveSubNet("1.2.3.4/240.0.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "0.0.0.0/4"); subnet = ResolveSubNet("1.2.3.4/224.0.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "0.0.0.0/3"); subnet = ResolveSubNet("1.2.3.4/192.0.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "0.0.0.0/2"); subnet = ResolveSubNet("1.2.3.4/128.0.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "0.0.0.0/1"); subnet = ResolveSubNet("1.2.3.4/0.0.0.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "0.0.0.0/0"); subnet = ResolveSubNet( "1:2:3:4:5:6:7:8/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); BOOST_CHECK_EQUAL(subnet.ToString(), "1:2:3:4:5:6:7:8/128"); subnet = ResolveSubNet( "1:2:3:4:5:6:7:8/ffff:0000:0000:0000:0000:0000:0000:0000"); BOOST_CHECK_EQUAL(subnet.ToString(), "1::/16"); subnet = ResolveSubNet( "1:2:3:4:5:6:7:8/0000:0000:0000:0000:0000:0000:0000:0000"); BOOST_CHECK_EQUAL(subnet.ToString(), "::/0"); subnet = ResolveSubNet("1.2.3.4/255.255.232.0"); BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/255.255.232.0"); subnet = ResolveSubNet( "1:2:3:4:5:6:7:8/ffff:ffff:ffff:fffe:ffff:ffff:ffff:ff0f"); BOOST_CHECK_EQUAL( subnet.ToString(), "1:2:3:4:5:6:7:8/ffff:ffff:ffff:fffe:ffff:ffff:ffff:ff0f"); } BOOST_AUTO_TEST_CASE(netbase_getgroup) { typedef std::vector Vec8; // Local -> !Routable() BOOST_CHECK(ResolveIP("127.0.0.1").GetGroup() == Vec8{0}); // !Valid -> !Routable() BOOST_CHECK(ResolveIP("257.0.0.1").GetGroup() == Vec8{0}); // RFC1918 -> !Routable() BOOST_CHECK(ResolveIP("10.0.0.1").GetGroup() == Vec8{0}); // RFC3927 -> !Routable() BOOST_CHECK(ResolveIP("169.254.1.1").GetGroup() == Vec8{0}); // IPv4 BOOST_CHECK(ResolveIP("1.2.3.4").GetGroup() == Vec8({NET_IPV4, 1, 2})); // RFC6145 BOOST_CHECK(ResolveIP("::FFFF:0:102:304").GetGroup() == Vec8({NET_IPV4, 1, 2})); // RFC6052 BOOST_CHECK(ResolveIP("64:FF9B::102:304").GetGroup() == Vec8({NET_IPV4, 1, 2})); // RFC3964 BOOST_CHECK(ResolveIP("2002:102:304:9999:9999:9999:9999:9999").GetGroup() == Vec8({NET_IPV4, 1, 2})); // RFC4380 BOOST_CHECK(ResolveIP("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup() == Vec8({NET_IPV4, 1, 2})); // Tor BOOST_CHECK( ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup() == Vec8({NET_ONION, 239})); // he.net BOOST_CHECK( ResolveIP("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup() == Vec8({NET_IPV6, 32, 1, 4, 112, 175})); // IPv6 BOOST_CHECK( ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup() == Vec8({NET_IPV6, 32, 1, 32, 1})); // baz.net sha256 hash: // 12929400eb4607c4ac075f087167e75286b179c693eb059a01774b864e8fe505 Vec8 internal_group = {NET_INTERNAL, 0x12, 0x92, 0x94, 0x00, 0xeb, 0x46, 0x07, 0xc4, 0xac, 0x07}; BOOST_CHECK(CreateInternal("baz.net").GetGroup() == internal_group); } +BOOST_AUTO_TEST_CASE(netbase_parsenetwork) { + BOOST_CHECK_EQUAL(ParseNetwork("ipv4"), NET_IPV4); + BOOST_CHECK_EQUAL(ParseNetwork("ipv6"), NET_IPV6); + BOOST_CHECK_EQUAL(ParseNetwork("onion"), NET_ONION); + BOOST_CHECK_EQUAL(ParseNetwork("tor"), NET_ONION); + + BOOST_CHECK_EQUAL(ParseNetwork("IPv4"), NET_IPV4); + BOOST_CHECK_EQUAL(ParseNetwork("IPv6"), NET_IPV6); + BOOST_CHECK_EQUAL(ParseNetwork("ONION"), NET_ONION); + BOOST_CHECK_EQUAL(ParseNetwork("TOR"), NET_ONION); + + BOOST_CHECK_EQUAL(ParseNetwork(":)"), NET_UNROUTABLE); + BOOST_CHECK_EQUAL(ParseNetwork("tÖr"), NET_UNROUTABLE); + BOOST_CHECK_EQUAL(ParseNetwork("\xfe\xff"), NET_UNROUTABLE); + BOOST_CHECK_EQUAL(ParseNetwork(""), NET_UNROUTABLE); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 7725eaf8e..2393a2d06 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -1,1388 +1,1424 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "util.h" #include "clientversion.h" #include "primitives/transaction.h" #include "sync.h" #include "test/test_bitcoin.h" #include "utilmoneystr.h" #include "utilstrencodings.h" #include #include #ifndef WIN32 #include #include #endif #include BOOST_FIXTURE_TEST_SUITE(util_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(util_criticalsection) { CCriticalSection cs; do { LOCK(cs); break; BOOST_ERROR("break was swallowed!"); } while (0); do { TRY_LOCK(cs, lockTest); if (lockTest) break; BOOST_ERROR("break was swallowed!"); } while (0); } static const uint8_t ParseHex_expected[65] = { 0x04, 0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, 0x48, 0x27, 0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, 0xb7, 0x10, 0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, 0x09, 0xa6, 0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, 0xde, 0xb6, 0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, 0x38, 0xc4, 0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, 0x12, 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f}; BOOST_AUTO_TEST_CASE(util_ParseHex) { std::vector result; std::vector expected( ParseHex_expected, ParseHex_expected + sizeof(ParseHex_expected)); // Basic test vector result = ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0" "ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d" "578a4c702b6bf11d5f"); BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); // Spaces between bytes must be supported result = ParseHex("12 34 56 78"); BOOST_CHECK(result.size() == 4 && result[0] == 0x12 && result[1] == 0x34 && result[2] == 0x56 && result[3] == 0x78); // Leading space must be supported (used in CDBEnv::Salvage) result = ParseHex(" 89 34 56 78"); BOOST_CHECK(result.size() == 4 && result[0] == 0x89 && result[1] == 0x34 && result[2] == 0x56 && result[3] == 0x78); // Stop parsing at invalid value result = ParseHex("1234 invalid 1234"); BOOST_CHECK(result.size() == 2 && result[0] == 0x12 && result[1] == 0x34); } BOOST_AUTO_TEST_CASE(util_HexStr) { BOOST_CHECK_EQUAL(HexStr(ParseHex_expected, ParseHex_expected + sizeof(ParseHex_expected)), "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0" "ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d" "578a4c702b6bf11d5f"); BOOST_CHECK_EQUAL(HexStr(ParseHex_expected, ParseHex_expected + 5, true), "04 67 8a fd b0"); BOOST_CHECK_EQUAL(HexStr(ParseHex_expected + sizeof(ParseHex_expected), ParseHex_expected + sizeof(ParseHex_expected)), ""); BOOST_CHECK_EQUAL(HexStr(ParseHex_expected + sizeof(ParseHex_expected), ParseHex_expected + sizeof(ParseHex_expected), true), ""); BOOST_CHECK_EQUAL(HexStr(ParseHex_expected, ParseHex_expected), ""); BOOST_CHECK_EQUAL(HexStr(ParseHex_expected, ParseHex_expected, true), ""); std::vector ParseHex_vec(ParseHex_expected, ParseHex_expected + 5); BOOST_CHECK_EQUAL(HexStr(ParseHex_vec, true), "04 67 8a fd b0"); BOOST_CHECK_EQUAL(HexStr(ParseHex_vec.rbegin(), ParseHex_vec.rend()), "b0fd8a6704"); BOOST_CHECK_EQUAL(HexStr(ParseHex_vec.rbegin(), ParseHex_vec.rend(), true), "b0 fd 8a 67 04"); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected), std::reverse_iterator(ParseHex_expected)), ""); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected), std::reverse_iterator(ParseHex_expected), true), ""); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected + 1), std::reverse_iterator(ParseHex_expected)), "04"); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected + 1), std::reverse_iterator(ParseHex_expected), true), "04"); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected + 5), std::reverse_iterator(ParseHex_expected)), "b0fd8a6704"); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected + 5), std::reverse_iterator(ParseHex_expected), true), "b0 fd 8a 67 04"); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected + 65), std::reverse_iterator(ParseHex_expected)), "5f1df16b2b704c8a578d0bbaf74d385cde12c11ee50455f3c438ef4c3fbcf649b6de61" "1feae06279a60939e028a8d65c10b73071a6f16719274855feb0fd8a6704"); } BOOST_AUTO_TEST_CASE(util_DateTimeStrFormat) { BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 0), "1970-01-01 00:00:00"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 0x7FFFFFFF), "2038-01-19 03:14:07"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 1317425777), "2011-09-30 23:36:17"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", 1317425777), "2011-09-30T23:36:17Z"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%H:%M:%SZ", 1317425777), "23:36:17Z"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M", 1317425777), "2011-09-30 23:36"); BOOST_CHECK_EQUAL( DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", 1317425777), "Fri, 30 Sep 2011 23:36:17 +0000"); } BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime) { BOOST_CHECK_EQUAL(FormatISO8601DateTime(1317425777), "2011-09-30T23:36:17Z"); } BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) { BOOST_CHECK_EQUAL(FormatISO8601Date(1317425777), "2011-09-30"); } BOOST_AUTO_TEST_CASE(util_FormatISO8601Time) { BOOST_CHECK_EQUAL(FormatISO8601Time(1317425777), "23:36:17Z"); } struct TestArgsManager : public ArgsManager { TestArgsManager() { m_network_only_args.clear(); } std::map> &GetOverrideArgs() { return m_override_args; } std::map> &GetConfigArgs() { return m_config_args; } void ReadConfigString(const std::string str_config) { std::istringstream streamConfig(str_config); { LOCK(cs_args); m_config_args.clear(); } ReadConfigStream(streamConfig); } void SetNetworkOnlyArg(const std::string arg) { LOCK(cs_args); m_network_only_args.insert(arg); } }; BOOST_AUTO_TEST_CASE(util_ParseParameters) { TestArgsManager testArgs; const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"}; testArgs.ParseParameters(0, (char **)argv_test); BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); testArgs.ParseParameters(1, (char **)argv_test); BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); testArgs.ParseParameters(5, (char **)argv_test); // expectation: -ignored is ignored (program name argument), // -a, -b and -ccc end up in map, -d ignored because it is after // a non-option argument (non-GNU option parsing) BOOST_CHECK(testArgs.GetOverrideArgs().size() == 3 && testArgs.GetConfigArgs().empty()); BOOST_CHECK(testArgs.IsArgSet("-a") && testArgs.IsArgSet("-b") && testArgs.IsArgSet("-ccc") && !testArgs.IsArgSet("f") && !testArgs.IsArgSet("-d")); BOOST_CHECK(testArgs.GetOverrideArgs().count("-a") && testArgs.GetOverrideArgs().count("-b") && testArgs.GetOverrideArgs().count("-ccc") && !testArgs.GetOverrideArgs().count("f") && !testArgs.GetOverrideArgs().count("-d")); BOOST_CHECK(testArgs.GetOverrideArgs()["-a"].size() == 1); BOOST_CHECK(testArgs.GetOverrideArgs()["-a"].front() == ""); BOOST_CHECK(testArgs.GetOverrideArgs()["-ccc"].size() == 2); BOOST_CHECK(testArgs.GetOverrideArgs()["-ccc"].front() == "argument"); BOOST_CHECK(testArgs.GetOverrideArgs()["-ccc"].back() == "multiple"); BOOST_CHECK(testArgs.GetArgs("-ccc").size() == 2); } BOOST_AUTO_TEST_CASE(util_GetBoolArg) { TestArgsManager testArgs; const char *argv_test[] = {"ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"}; testArgs.ParseParameters(7, (char **)argv_test); // Each letter should be set. for (char opt : "abcdef") { BOOST_CHECK(testArgs.IsArgSet({'-', opt}) || !opt); } // Nothing else should be in the map BOOST_CHECK(testArgs.GetOverrideArgs().size() == 6 && testArgs.GetConfigArgs().empty()); // The -no prefix should get stripped on the way in. BOOST_CHECK(!testArgs.IsArgSet("-nob")); // The -b option is flagged as negated, and nothing else is BOOST_CHECK(testArgs.IsArgNegated("-b")); BOOST_CHECK(!testArgs.IsArgNegated("-a")); // Check expected values. BOOST_CHECK(testArgs.GetBoolArg("-a", false) == true); BOOST_CHECK(testArgs.GetBoolArg("-b", true) == false); BOOST_CHECK(testArgs.GetBoolArg("-c", true) == false); BOOST_CHECK(testArgs.GetBoolArg("-d", false) == true); BOOST_CHECK(testArgs.GetBoolArg("-e", true) == false); BOOST_CHECK(testArgs.GetBoolArg("-f", true) == false); } BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) { // Test some awful edge cases that hopefully no user will ever exercise. TestArgsManager testArgs; // Params test const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"}; testArgs.ParseParameters(4, (char **)argv_test); // This was passed twice, second one overrides the negative setting. BOOST_CHECK(!testArgs.IsArgNegated("-foo")); BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == ""); // A double negative is a positive, and not marked as negated. BOOST_CHECK(!testArgs.IsArgNegated("-bar")); BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1"); // Config test const char *conf_test = "nofoo=1\nfoo=1\nnobar=0\n"; testArgs.ParseParameters(1, (char **)argv_test); testArgs.ReadConfigString(conf_test); // This was passed twice, second one overrides the negative setting, // and the value. BOOST_CHECK(!testArgs.IsArgNegated("-foo")); BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "1"); // A double negative is a positive, and does not count as negated. BOOST_CHECK(!testArgs.IsArgNegated("-bar")); BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1"); // Combined test const char *combo_test_args[] = {"ignored", "-nofoo", "-bar"}; const char *combo_test_conf = "foo=1\nnobar=1\n"; testArgs.ParseParameters(3, (char **)combo_test_args); testArgs.ReadConfigString(combo_test_conf); // Command line overrides, but doesn't erase old setting BOOST_CHECK(testArgs.IsArgNegated("-foo")); BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "0"); BOOST_CHECK(testArgs.GetArgs("-foo").size() == 0); // Command line overrides, but doesn't erase old setting BOOST_CHECK(!testArgs.IsArgNegated("-bar")); BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == ""); BOOST_CHECK(testArgs.GetArgs("-bar").size() == 1 && testArgs.GetArgs("-bar").front() == ""); } BOOST_AUTO_TEST_CASE(util_ReadConfigStream) { const char *str_config = "a=\n" "b=1\n" "ccc=argument\n" "ccc=multiple\n" "d=e\n" "nofff=1\n" "noggg=0\n" "h=1\n" "noh=1\n" "noi=1\n" "i=1\n" "sec1.ccc=extend1\n" "\n" "[sec1]\n" "ccc=extend2\n" "d=eee\n" "h=1\n" "[sec2]\n" "ccc=extend3\n" "iii=2\n"; TestArgsManager test_args; test_args.ReadConfigString(str_config); // expectation: a, b, ccc, d, fff, ggg, h, i end up in map // so do sec1.ccc, sec1.d, sec1.h, sec2.ccc, sec2.iii BOOST_CHECK(test_args.GetOverrideArgs().empty()); BOOST_CHECK(test_args.GetConfigArgs().size() == 13); BOOST_CHECK(test_args.GetConfigArgs().count("-a") && test_args.GetConfigArgs().count("-b") && test_args.GetConfigArgs().count("-ccc") && test_args.GetConfigArgs().count("-d") && test_args.GetConfigArgs().count("-fff") && test_args.GetConfigArgs().count("-ggg") && test_args.GetConfigArgs().count("-h") && test_args.GetConfigArgs().count("-i")); BOOST_CHECK(test_args.GetConfigArgs().count("-sec1.ccc") && test_args.GetConfigArgs().count("-sec1.h") && test_args.GetConfigArgs().count("-sec2.ccc") && test_args.GetConfigArgs().count("-sec2.iii")); BOOST_CHECK(test_args.IsArgSet("-a") && test_args.IsArgSet("-b") && test_args.IsArgSet("-ccc") && test_args.IsArgSet("-d") && test_args.IsArgSet("-fff") && test_args.IsArgSet("-ggg") && test_args.IsArgSet("-h") && test_args.IsArgSet("-i") && !test_args.IsArgSet("-zzz") && !test_args.IsArgSet("-iii")); BOOST_CHECK(test_args.GetArg("-a", "xxx") == "" && test_args.GetArg("-b", "xxx") == "1" && test_args.GetArg("-ccc", "xxx") == "argument" && test_args.GetArg("-d", "xxx") == "e" && test_args.GetArg("-fff", "xxx") == "0" && test_args.GetArg("-ggg", "xxx") == "1" && test_args.GetArg("-h", "xxx") == "0" && test_args.GetArg("-i", "xxx") == "1" && test_args.GetArg("-zzz", "xxx") == "xxx" && test_args.GetArg("-iii", "xxx") == "xxx"); for (bool def : {false, true}) { BOOST_CHECK(test_args.GetBoolArg("-a", def) && test_args.GetBoolArg("-b", def) && !test_args.GetBoolArg("-ccc", def) && !test_args.GetBoolArg("-d", def) && !test_args.GetBoolArg("-fff", def) && test_args.GetBoolArg("-ggg", def) && !test_args.GetBoolArg("-h", def) && test_args.GetBoolArg("-i", def) && test_args.GetBoolArg("-zzz", def) == def && test_args.GetBoolArg("-iii", def) == def); } BOOST_CHECK(test_args.GetArgs("-a").size() == 1 && test_args.GetArgs("-a").front() == ""); BOOST_CHECK(test_args.GetArgs("-b").size() == 1 && test_args.GetArgs("-b").front() == "1"); BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2 && test_args.GetArgs("-ccc").front() == "argument" && test_args.GetArgs("-ccc").back() == "multiple"); BOOST_CHECK(test_args.GetArgs("-fff").size() == 0); BOOST_CHECK(test_args.GetArgs("-nofff").size() == 0); BOOST_CHECK(test_args.GetArgs("-ggg").size() == 1 && test_args.GetArgs("-ggg").front() == "1"); BOOST_CHECK(test_args.GetArgs("-noggg").size() == 0); BOOST_CHECK(test_args.GetArgs("-h").size() == 0); BOOST_CHECK(test_args.GetArgs("-noh").size() == 0); BOOST_CHECK(test_args.GetArgs("-i").size() == 1 && test_args.GetArgs("-i").front() == "1"); BOOST_CHECK(test_args.GetArgs("-noi").size() == 0); BOOST_CHECK(test_args.GetArgs("-zzz").size() == 0); BOOST_CHECK(!test_args.IsArgNegated("-a")); BOOST_CHECK(!test_args.IsArgNegated("-b")); BOOST_CHECK(!test_args.IsArgNegated("-ccc")); BOOST_CHECK(!test_args.IsArgNegated("-d")); BOOST_CHECK(test_args.IsArgNegated("-fff")); BOOST_CHECK(!test_args.IsArgNegated("-ggg")); // last setting takes precedence BOOST_CHECK(test_args.IsArgNegated("-h")); // last setting takes precedence BOOST_CHECK(!test_args.IsArgNegated("-i")); BOOST_CHECK(!test_args.IsArgNegated("-zzz")); // Test sections work test_args.SelectConfigNetwork("sec1"); // same as original BOOST_CHECK(test_args.GetArg("-a", "xxx") == "" && test_args.GetArg("-b", "xxx") == "1" && test_args.GetArg("-fff", "xxx") == "0" && test_args.GetArg("-ggg", "xxx") == "1" && test_args.GetArg("-zzz", "xxx") == "xxx" && test_args.GetArg("-iii", "xxx") == "xxx"); // d is overridden BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee"); // section-specific setting BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1"); // section takes priority for multiple values BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend1"); // check multiple values works const std::vector sec1_ccc_expected = {"extend1", "extend2", "argument", "multiple"}; const auto &sec1_ccc_res = test_args.GetArgs("-ccc"); BOOST_CHECK_EQUAL_COLLECTIONS(sec1_ccc_res.begin(), sec1_ccc_res.end(), sec1_ccc_expected.begin(), sec1_ccc_expected.end()); test_args.SelectConfigNetwork("sec2"); // same as original BOOST_CHECK(test_args.GetArg("-a", "xxx") == "" && test_args.GetArg("-b", "xxx") == "1" && test_args.GetArg("-d", "xxx") == "e" && test_args.GetArg("-fff", "xxx") == "0" && test_args.GetArg("-ggg", "xxx") == "1" && test_args.GetArg("-zzz", "xxx") == "xxx" && test_args.GetArg("-h", "xxx") == "0"); // section-specific setting BOOST_CHECK(test_args.GetArg("-iii", "xxx") == "2"); // section takes priority for multiple values BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend3"); // check multiple values works const std::vector sec2_ccc_expected = {"extend3", "argument", "multiple"}; const auto &sec2_ccc_res = test_args.GetArgs("-ccc"); BOOST_CHECK_EQUAL_COLLECTIONS(sec2_ccc_res.begin(), sec2_ccc_res.end(), sec2_ccc_expected.begin(), sec2_ccc_expected.end()); // Test section only options test_args.SetNetworkOnlyArg("-d"); test_args.SetNetworkOnlyArg("-ccc"); test_args.SetNetworkOnlyArg("-h"); test_args.SelectConfigNetwork(CBaseChainParams::MAIN); BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e"); BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2); BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0"); test_args.SelectConfigNetwork("sec1"); BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee"); BOOST_CHECK(test_args.GetArgs("-d").size() == 1); BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2); BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1"); test_args.SelectConfigNetwork("sec2"); BOOST_CHECK(test_args.GetArg("-d", "xxx") == "xxx"); BOOST_CHECK(test_args.GetArgs("-d").size() == 0); BOOST_CHECK(test_args.GetArgs("-ccc").size() == 1); BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0"); } BOOST_AUTO_TEST_CASE(util_GetArg) { TestArgsManager testArgs; testArgs.GetOverrideArgs().clear(); testArgs.GetOverrideArgs()["strtest1"] = {"string..."}; // strtest2 undefined on purpose testArgs.GetOverrideArgs()["inttest1"] = {"12345"}; testArgs.GetOverrideArgs()["inttest2"] = {"81985529216486895"}; // inttest3 undefined on purpose testArgs.GetOverrideArgs()["booltest1"] = {""}; // booltest2 undefined on purpose testArgs.GetOverrideArgs()["booltest3"] = {"0"}; testArgs.GetOverrideArgs()["booltest4"] = {"1"}; // priorities testArgs.GetOverrideArgs()["pritest1"] = {"a", "b"}; testArgs.GetConfigArgs()["pritest2"] = {"a", "b"}; testArgs.GetOverrideArgs()["pritest3"] = {"a"}; testArgs.GetConfigArgs()["pritest3"] = {"b"}; testArgs.GetOverrideArgs()["pritest4"] = {"a", "b"}; testArgs.GetConfigArgs()["pritest4"] = {"c", "d"}; BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); BOOST_CHECK_EQUAL(testArgs.GetArg("inttest1", -1), 12345); BOOST_CHECK_EQUAL(testArgs.GetArg("inttest2", -1), 81985529216486895LL); BOOST_CHECK_EQUAL(testArgs.GetArg("inttest3", -1), -1); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest2", false), false); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest3", false), false); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest4", false), true); BOOST_CHECK_EQUAL(testArgs.GetArg("pritest1", "default"), "b"); BOOST_CHECK_EQUAL(testArgs.GetArg("pritest2", "default"), "a"); BOOST_CHECK_EQUAL(testArgs.GetArg("pritest3", "default"), "a"); BOOST_CHECK_EQUAL(testArgs.GetArg("pritest4", "default"), "b"); } BOOST_AUTO_TEST_CASE(util_ClearArg) { TestArgsManager testArgs; // Clear single string arg testArgs.GetOverrideArgs()["strtest1"] = {"string..."}; BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string..."); testArgs.ClearArg("strtest1"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "default"); // Clear boolean arg testArgs.GetOverrideArgs()["booltest1"] = {"1"}; BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true); testArgs.ClearArg("booltest1"); BOOST_CHECK_EQUAL(testArgs.GetArg("booltest1", false), false); // Clear config args only testArgs.GetConfigArgs()["strtest2"].push_back("string..."); testArgs.GetConfigArgs()["strtest2"].push_back("...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 2); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").back(), "...gnirts"); testArgs.ClearArg("strtest2"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 0); // Clear both cli args and config args testArgs.GetOverrideArgs()["strtest3"].push_back("cli string..."); testArgs.GetOverrideArgs()["strtest3"].push_back("...gnirts ilc"); testArgs.GetConfigArgs()["strtest3"].push_back("string..."); testArgs.GetConfigArgs()["strtest3"].push_back("...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest3", "default"), "...gnirts ilc"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest3").size(), 4); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest3").front(), "cli string..."); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest3").back(), "...gnirts"); testArgs.ClearArg("strtest3"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest3", "default"), "default"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest3").size(), 0); } BOOST_AUTO_TEST_CASE(util_SetArg) { TestArgsManager testArgs; // SoftSetArg BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "default"); BOOST_CHECK_EQUAL(testArgs.SoftSetArg("strtest1", "string..."), true); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest1").size(), 1); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest1").front(), "string..."); BOOST_CHECK_EQUAL(testArgs.SoftSetArg("strtest1", "...gnirts"), false); testArgs.ClearArg("strtest1"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "default"); BOOST_CHECK_EQUAL(testArgs.SoftSetArg("strtest1", "...gnirts"), true); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "...gnirts"); // SoftSetBoolArg BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), false); BOOST_CHECK_EQUAL(testArgs.SoftSetBoolArg("booltest1", true), true); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true); BOOST_CHECK_EQUAL(testArgs.SoftSetBoolArg("booltest1", false), false); testArgs.ClearArg("booltest1"); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", true), true); BOOST_CHECK_EQUAL(testArgs.SoftSetBoolArg("booltest1", false), true); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", true), false); // ForceSetArg BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); testArgs.ForceSetArg("strtest2", "string..."); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 1); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "string..."); testArgs.ForceSetArg("strtest2", "...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 1); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "...gnirts"); // ForceSetMultiArg testArgs.ForceSetMultiArg("strtest2", "string..."); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 2); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").back(), "string..."); testArgs.ClearArg("strtest2"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 0); testArgs.ForceSetMultiArg("strtest2", "string..."); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 1); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "string..."); testArgs.ForceSetMultiArg("strtest2", "one more thing..."); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "one more thing..."); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 2); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").back(), "one more thing..."); // If there are multi args, ForceSetArg should erase them testArgs.ForceSetArg("strtest2", "...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 1); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "...gnirts"); } BOOST_AUTO_TEST_CASE(util_GetChainName) { TestArgsManager test_args; const char *argv_testnet[] = {"cmd", "-testnet"}; const char *argv_regtest[] = {"cmd", "-regtest"}; const char *argv_test_no_reg[] = {"cmd", "-testnet", "-noregtest"}; const char *argv_both[] = {"cmd", "-testnet", "-regtest"}; // equivalent to "-testnet" // regtest in testnet section is ignored const char *testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1"; test_args.ParseParameters(0, (char **)argv_testnet); BOOST_CHECK_EQUAL(test_args.GetChainName(), "main"); test_args.ParseParameters(2, (char **)argv_testnet); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); test_args.ParseParameters(2, (char **)argv_regtest); BOOST_CHECK_EQUAL(test_args.GetChainName(), "regtest"); test_args.ParseParameters(3, (char **)argv_test_no_reg); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); test_args.ParseParameters(3, (char **)argv_both); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); test_args.ParseParameters(0, (char **)argv_testnet); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); test_args.ParseParameters(2, (char **)argv_testnet); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); test_args.ParseParameters(2, (char **)argv_regtest); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); test_args.ParseParameters(3, (char **)argv_test_no_reg); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); test_args.ParseParameters(3, (char **)argv_both); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); // check setting the network to test (and thus making // [test] regtest=1 potentially relevent) doesn't break things test_args.SelectConfigNetwork("test"); test_args.ParseParameters(0, (char **)argv_testnet); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); test_args.ParseParameters(2, (char **)argv_testnet); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); test_args.ParseParameters(2, (char **)argv_regtest); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); test_args.ParseParameters(2, (char **)argv_test_no_reg); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); test_args.ParseParameters(3, (char **)argv_both); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); } BOOST_AUTO_TEST_CASE(util_FormatMoney) { BOOST_CHECK_EQUAL(FormatMoney(Amount::zero()), "0.00"); BOOST_CHECK_EQUAL(FormatMoney(123456789 * (COIN / 10000)), "12345.6789"); BOOST_CHECK_EQUAL(FormatMoney(-1 * COIN), "-1.00"); BOOST_CHECK_EQUAL(FormatMoney(100000000 * COIN), "100000000.00"); BOOST_CHECK_EQUAL(FormatMoney(10000000 * COIN), "10000000.00"); BOOST_CHECK_EQUAL(FormatMoney(1000000 * COIN), "1000000.00"); BOOST_CHECK_EQUAL(FormatMoney(100000 * COIN), "100000.00"); BOOST_CHECK_EQUAL(FormatMoney(10000 * COIN), "10000.00"); BOOST_CHECK_EQUAL(FormatMoney(1000 * COIN), "1000.00"); BOOST_CHECK_EQUAL(FormatMoney(100 * COIN), "100.00"); BOOST_CHECK_EQUAL(FormatMoney(10 * COIN), "10.00"); BOOST_CHECK_EQUAL(FormatMoney(COIN), "1.00"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 10), "0.10"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 100), "0.01"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 1000), "0.001"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 10000), "0.0001"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 100000), "0.00001"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 1000000), "0.000001"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 10000000), "0.0000001"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 100000000), "0.00000001"); } BOOST_AUTO_TEST_CASE(util_ParseMoney) { Amount ret = Amount::zero(); BOOST_CHECK(ParseMoney("0.0", ret)); BOOST_CHECK_EQUAL(ret, Amount::zero()); BOOST_CHECK(ParseMoney("12345.6789", ret)); BOOST_CHECK_EQUAL(ret, 123456789 * (COIN / 10000)); BOOST_CHECK(ParseMoney("100000000.00", ret)); BOOST_CHECK_EQUAL(ret, 100000000 * COIN); BOOST_CHECK(ParseMoney("10000000.00", ret)); BOOST_CHECK_EQUAL(ret, 10000000 * COIN); BOOST_CHECK(ParseMoney("1000000.00", ret)); BOOST_CHECK_EQUAL(ret, 1000000 * COIN); BOOST_CHECK(ParseMoney("100000.00", ret)); BOOST_CHECK_EQUAL(ret, 100000 * COIN); BOOST_CHECK(ParseMoney("10000.00", ret)); BOOST_CHECK_EQUAL(ret, 10000 * COIN); BOOST_CHECK(ParseMoney("1000.00", ret)); BOOST_CHECK_EQUAL(ret, 1000 * COIN); BOOST_CHECK(ParseMoney("100.00", ret)); BOOST_CHECK_EQUAL(ret, 100 * COIN); BOOST_CHECK(ParseMoney("10.00", ret)); BOOST_CHECK_EQUAL(ret, 10 * COIN); BOOST_CHECK(ParseMoney("1.00", ret)); BOOST_CHECK_EQUAL(ret, COIN); BOOST_CHECK(ParseMoney("1", ret)); BOOST_CHECK_EQUAL(ret, COIN); BOOST_CHECK(ParseMoney("0.1", ret)); BOOST_CHECK_EQUAL(ret, COIN / 10); BOOST_CHECK(ParseMoney("0.01", ret)); BOOST_CHECK_EQUAL(ret, COIN / 100); BOOST_CHECK(ParseMoney("0.001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 1000); BOOST_CHECK(ParseMoney("0.0001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 10000); BOOST_CHECK(ParseMoney("0.00001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 100000); BOOST_CHECK(ParseMoney("0.000001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 1000000); BOOST_CHECK(ParseMoney("0.0000001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 10000000); BOOST_CHECK(ParseMoney("0.00000001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 100000000); // Attempted 63 bit overflow should fail BOOST_CHECK(!ParseMoney("92233720368.54775808", ret)); // Parsing negative amounts must fail BOOST_CHECK(!ParseMoney("-1", ret)); } BOOST_AUTO_TEST_CASE(util_IsHex) { BOOST_CHECK(IsHex("00")); BOOST_CHECK(IsHex("00112233445566778899aabbccddeeffAABBCCDDEEFF")); BOOST_CHECK(IsHex("ff")); BOOST_CHECK(IsHex("FF")); BOOST_CHECK(!IsHex("")); BOOST_CHECK(!IsHex("0")); BOOST_CHECK(!IsHex("a")); BOOST_CHECK(!IsHex("eleven")); BOOST_CHECK(!IsHex("00xx00")); BOOST_CHECK(!IsHex("0x0000")); } BOOST_AUTO_TEST_CASE(util_IsHexNumber) { BOOST_CHECK(IsHexNumber("0x0")); BOOST_CHECK(IsHexNumber("0")); BOOST_CHECK(IsHexNumber("0x10")); BOOST_CHECK(IsHexNumber("10")); BOOST_CHECK(IsHexNumber("0xff")); BOOST_CHECK(IsHexNumber("ff")); BOOST_CHECK(IsHexNumber("0xFfa")); BOOST_CHECK(IsHexNumber("Ffa")); BOOST_CHECK(IsHexNumber("0x00112233445566778899aabbccddeeffAABBCCDDEEFF")); BOOST_CHECK(IsHexNumber("00112233445566778899aabbccddeeffAABBCCDDEEFF")); BOOST_CHECK(!IsHexNumber("")); // empty string not allowed BOOST_CHECK(!IsHexNumber("0x")); // empty string after prefix not allowed BOOST_CHECK(!IsHexNumber("0x0 ")); // no spaces at end, BOOST_CHECK(!IsHexNumber(" 0x0")); // or beginning, BOOST_CHECK(!IsHexNumber("0x 0")); // or middle, BOOST_CHECK(!IsHexNumber(" ")); // etc. BOOST_CHECK(!IsHexNumber("0x0ga")); // invalid character BOOST_CHECK(!IsHexNumber("x0")); // broken prefix BOOST_CHECK(!IsHexNumber("0x0x00")); // two prefixes not allowed } BOOST_AUTO_TEST_CASE(util_seed_insecure_rand) { SeedInsecureRand(true); for (int mod = 2; mod < 11; mod++) { int mask = 1; // Really rough binomal confidence approximation. int err = 30 * 10000. / mod * sqrt((1. / mod * (1 - 1. / mod)) / 10000.); // mask is 2^ceil(log2(mod))-1 while (mask < mod - 1) mask = (mask << 1) + 1; int count = 0; // How often does it get a zero from the uniform range [0,mod)? for (int i = 0; i < 10000; i++) { uint32_t rval; do { rval = insecure_rand() & mask; } while (rval >= (uint32_t)mod); count += rval == 0; } BOOST_CHECK(count <= 10000 / mod + err); BOOST_CHECK(count >= 10000 / mod - err); } } BOOST_AUTO_TEST_CASE(util_TimingResistantEqual) { BOOST_CHECK(TimingResistantEqual(std::string(""), std::string(""))); BOOST_CHECK(!TimingResistantEqual(std::string("abc"), std::string(""))); BOOST_CHECK(!TimingResistantEqual(std::string(""), std::string("abc"))); BOOST_CHECK(!TimingResistantEqual(std::string("a"), std::string("aa"))); BOOST_CHECK(!TimingResistantEqual(std::string("aa"), std::string("a"))); BOOST_CHECK(TimingResistantEqual(std::string("abc"), std::string("abc"))); BOOST_CHECK(!TimingResistantEqual(std::string("abc"), std::string("aba"))); } /* Test strprintf formatting directives. * Put a string before and after to ensure sanity of element sizes on stack. */ #define B "check_prefix" #define E "check_postfix" BOOST_AUTO_TEST_CASE(strprintf_numbers) { int64_t s64t = -9223372036854775807LL; /* signed 64 bit test value */ uint64_t u64t = 18446744073709551615ULL; /* unsigned 64 bit test value */ BOOST_CHECK(strprintf("%s %d %s", B, s64t, E) == B " -9223372036854775807 " E); BOOST_CHECK(strprintf("%s %u %s", B, u64t, E) == B " 18446744073709551615 " E); BOOST_CHECK(strprintf("%s %x %s", B, u64t, E) == B " ffffffffffffffff " E); size_t st = 12345678; /* unsigned size_t test value */ ssize_t sst = -12345678; /* signed size_t test value */ BOOST_CHECK(strprintf("%s %d %s", B, sst, E) == B " -12345678 " E); BOOST_CHECK(strprintf("%s %u %s", B, st, E) == B " 12345678 " E); BOOST_CHECK(strprintf("%s %x %s", B, st, E) == B " bc614e " E); ptrdiff_t pt = 87654321; /* positive ptrdiff_t test value */ ptrdiff_t spt = -87654321; /* negative ptrdiff_t test value */ BOOST_CHECK(strprintf("%s %d %s", B, spt, E) == B " -87654321 " E); BOOST_CHECK(strprintf("%s %u %s", B, pt, E) == B " 87654321 " E); BOOST_CHECK(strprintf("%s %x %s", B, pt, E) == B " 5397fb1 " E); } #undef B #undef E /* Check for mingw/wine issue #3494 * Remove this test before time.ctime(0xffffffff) == 'Sun Feb 7 07:28:15 2106' */ BOOST_AUTO_TEST_CASE(gettime) { BOOST_CHECK((GetTime() & ~0xFFFFFFFFLL) == 0); } BOOST_AUTO_TEST_CASE(test_ParseInt32) { int32_t n; // Valid values BOOST_CHECK(ParseInt32("1234", nullptr)); BOOST_CHECK(ParseInt32("0", &n) && n == 0); BOOST_CHECK(ParseInt32("1234", &n) && n == 1234); BOOST_CHECK(ParseInt32("01234", &n) && n == 1234); // no octal BOOST_CHECK(ParseInt32("2147483647", &n) && n == 2147483647); BOOST_CHECK(ParseInt32("-2147483648", &n) && n == -2147483648); BOOST_CHECK(ParseInt32("-1234", &n) && n == -1234); // Invalid values BOOST_CHECK(!ParseInt32("", &n)); BOOST_CHECK(!ParseInt32(" 1", &n)); // no padding inside BOOST_CHECK(!ParseInt32("1 ", &n)); BOOST_CHECK(!ParseInt32("1a", &n)); BOOST_CHECK(!ParseInt32("aap", &n)); BOOST_CHECK(!ParseInt32("0x1", &n)); // no hex BOOST_CHECK(!ParseInt32("0x1", &n)); // no hex const char test_bytes[] = {'1', 0, '1'}; std::string teststr(test_bytes, sizeof(test_bytes)); BOOST_CHECK(!ParseInt32(teststr, &n)); // no embedded NULs // Overflow and underflow BOOST_CHECK(!ParseInt32("-2147483649", nullptr)); BOOST_CHECK(!ParseInt32("2147483648", nullptr)); BOOST_CHECK(!ParseInt32("-32482348723847471234", nullptr)); BOOST_CHECK(!ParseInt32("32482348723847471234", nullptr)); } BOOST_AUTO_TEST_CASE(test_ParseInt64) { int64_t n; // Valid values BOOST_CHECK(ParseInt64("1234", nullptr)); BOOST_CHECK(ParseInt64("0", &n) && n == 0LL); BOOST_CHECK(ParseInt64("1234", &n) && n == 1234LL); BOOST_CHECK(ParseInt64("01234", &n) && n == 1234LL); // no octal BOOST_CHECK(ParseInt64("2147483647", &n) && n == 2147483647LL); BOOST_CHECK(ParseInt64("-2147483648", &n) && n == -2147483648LL); BOOST_CHECK(ParseInt64("9223372036854775807", &n) && n == (int64_t)9223372036854775807); BOOST_CHECK(ParseInt64("-9223372036854775808", &n) && n == (int64_t)-9223372036854775807 - 1); BOOST_CHECK(ParseInt64("-1234", &n) && n == -1234LL); // Invalid values BOOST_CHECK(!ParseInt64("", &n)); BOOST_CHECK(!ParseInt64(" 1", &n)); // no padding inside BOOST_CHECK(!ParseInt64("1 ", &n)); BOOST_CHECK(!ParseInt64("1a", &n)); BOOST_CHECK(!ParseInt64("aap", &n)); BOOST_CHECK(!ParseInt64("0x1", &n)); // no hex const char test_bytes[] = {'1', 0, '1'}; std::string teststr(test_bytes, sizeof(test_bytes)); BOOST_CHECK(!ParseInt64(teststr, &n)); // no embedded NULs // Overflow and underflow BOOST_CHECK(!ParseInt64("-9223372036854775809", nullptr)); BOOST_CHECK(!ParseInt64("9223372036854775808", nullptr)); BOOST_CHECK(!ParseInt64("-32482348723847471234", nullptr)); BOOST_CHECK(!ParseInt64("32482348723847471234", nullptr)); } BOOST_AUTO_TEST_CASE(test_ParseUInt32) { uint32_t n; // Valid values BOOST_CHECK(ParseUInt32("1234", nullptr)); BOOST_CHECK(ParseUInt32("0", &n) && n == 0); BOOST_CHECK(ParseUInt32("1234", &n) && n == 1234); BOOST_CHECK(ParseUInt32("01234", &n) && n == 1234); // no octal BOOST_CHECK(ParseUInt32("2147483647", &n) && n == 2147483647); BOOST_CHECK(ParseUInt32("2147483648", &n) && n == (uint32_t)2147483648); BOOST_CHECK(ParseUInt32("4294967295", &n) && n == (uint32_t)4294967295); // Invalid values BOOST_CHECK(!ParseUInt32("", &n)); BOOST_CHECK(!ParseUInt32(" 1", &n)); // no padding inside BOOST_CHECK(!ParseUInt32(" -1", &n)); BOOST_CHECK(!ParseUInt32("1 ", &n)); BOOST_CHECK(!ParseUInt32("1a", &n)); BOOST_CHECK(!ParseUInt32("aap", &n)); BOOST_CHECK(!ParseUInt32("0x1", &n)); // no hex BOOST_CHECK(!ParseUInt32("0x1", &n)); // no hex const char test_bytes[] = {'1', 0, '1'}; std::string teststr(test_bytes, sizeof(test_bytes)); BOOST_CHECK(!ParseUInt32(teststr, &n)); // no embedded NULs // Overflow and underflow BOOST_CHECK(!ParseUInt32("-2147483648", &n)); BOOST_CHECK(!ParseUInt32("4294967296", &n)); BOOST_CHECK(!ParseUInt32("-1234", &n)); BOOST_CHECK(!ParseUInt32("-32482348723847471234", nullptr)); BOOST_CHECK(!ParseUInt32("32482348723847471234", nullptr)); } BOOST_AUTO_TEST_CASE(test_ParseUInt64) { uint64_t n; // Valid values BOOST_CHECK(ParseUInt64("1234", nullptr)); BOOST_CHECK(ParseUInt64("0", &n) && n == 0LL); BOOST_CHECK(ParseUInt64("1234", &n) && n == 1234LL); BOOST_CHECK(ParseUInt64("01234", &n) && n == 1234LL); // no octal BOOST_CHECK(ParseUInt64("2147483647", &n) && n == 2147483647LL); BOOST_CHECK(ParseUInt64("9223372036854775807", &n) && n == 9223372036854775807ULL); BOOST_CHECK(ParseUInt64("9223372036854775808", &n) && n == 9223372036854775808ULL); BOOST_CHECK(ParseUInt64("18446744073709551615", &n) && n == 18446744073709551615ULL); // Invalid values BOOST_CHECK(!ParseUInt64("", &n)); BOOST_CHECK(!ParseUInt64(" 1", &n)); // no padding inside BOOST_CHECK(!ParseUInt64(" -1", &n)); BOOST_CHECK(!ParseUInt64("1 ", &n)); BOOST_CHECK(!ParseUInt64("1a", &n)); BOOST_CHECK(!ParseUInt64("aap", &n)); BOOST_CHECK(!ParseUInt64("0x1", &n)); // no hex const char test_bytes[] = {'1', 0, '1'}; std::string teststr(test_bytes, sizeof(test_bytes)); BOOST_CHECK(!ParseUInt64(teststr, &n)); // no embedded NULs // Overflow and underflow BOOST_CHECK(!ParseUInt64("-9223372036854775809", nullptr)); BOOST_CHECK(!ParseUInt64("18446744073709551616", nullptr)); BOOST_CHECK(!ParseUInt64("-32482348723847471234", nullptr)); BOOST_CHECK(!ParseUInt64("-2147483648", &n)); BOOST_CHECK(!ParseUInt64("-9223372036854775808", &n)); BOOST_CHECK(!ParseUInt64("-1234", &n)); } BOOST_AUTO_TEST_CASE(test_ParseDouble) { double n; // Valid values BOOST_CHECK(ParseDouble("1234", nullptr)); BOOST_CHECK(ParseDouble("0", &n) && n == 0.0); BOOST_CHECK(ParseDouble("1234", &n) && n == 1234.0); BOOST_CHECK(ParseDouble("01234", &n) && n == 1234.0); // no octal BOOST_CHECK(ParseDouble("2147483647", &n) && n == 2147483647.0); BOOST_CHECK(ParseDouble("-2147483648", &n) && n == -2147483648.0); BOOST_CHECK(ParseDouble("-1234", &n) && n == -1234.0); BOOST_CHECK(ParseDouble("1e6", &n) && n == 1e6); BOOST_CHECK(ParseDouble("-1e6", &n) && n == -1e6); // Invalid values BOOST_CHECK(!ParseDouble("", &n)); BOOST_CHECK(!ParseDouble(" 1", &n)); // no padding inside BOOST_CHECK(!ParseDouble("1 ", &n)); BOOST_CHECK(!ParseDouble("1a", &n)); BOOST_CHECK(!ParseDouble("aap", &n)); BOOST_CHECK(!ParseDouble("0x1", &n)); // no hex const char test_bytes[] = {'1', 0, '1'}; std::string teststr(test_bytes, sizeof(test_bytes)); BOOST_CHECK(!ParseDouble(teststr, &n)); // no embedded NULs // Overflow and underflow BOOST_CHECK(!ParseDouble("-1e10000", nullptr)); BOOST_CHECK(!ParseDouble("1e10000", nullptr)); } BOOST_AUTO_TEST_CASE(test_FormatParagraph) { BOOST_CHECK_EQUAL(FormatParagraph("", 79, 0), ""); BOOST_CHECK_EQUAL(FormatParagraph("test", 79, 0), "test"); BOOST_CHECK_EQUAL(FormatParagraph(" test", 79, 0), " test"); BOOST_CHECK_EQUAL(FormatParagraph("test test", 79, 0), "test test"); BOOST_CHECK_EQUAL(FormatParagraph("test test", 4, 0), "test\ntest"); BOOST_CHECK_EQUAL(FormatParagraph("testerde test", 4, 0), "testerde\ntest"); BOOST_CHECK_EQUAL(FormatParagraph("test test", 4, 4), "test\n test"); // Make sure we don't indent a fully-new line following a too-long line // ending BOOST_CHECK_EQUAL(FormatParagraph("test test\nabc", 4, 4), "test\n test\nabc"); BOOST_CHECK_EQUAL( FormatParagraph("This_is_a_very_long_test_string_without_any_spaces_so_" "it_should_just_get_returned_as_is_despite_the_length " "until it gets here", 79), "This_is_a_very_long_test_string_without_any_spaces_so_it_should_just_" "get_returned_as_is_despite_the_length\nuntil it gets here"); // Test wrap length is exact BOOST_CHECK_EQUAL( FormatParagraph("a b c d e f g h i j k l m n o p q r s t u v w x y z 1 " "2 3 4 5 6 7 8 9 a b c de f g h i j k l m n o p", 79), "a b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 8 9 " "a b c de\nf g h i j k l m n o p"); BOOST_CHECK_EQUAL( FormatParagraph("x\na b c d e f g h i j k l m n o p q r s t u v w x y " "z 1 2 3 4 5 6 7 8 9 a b c de f g h i j k l m n o p", 79), "x\na b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 " "8 9 a b c de\nf g h i j k l m n o p"); // Indent should be included in length of lines BOOST_CHECK_EQUAL( FormatParagraph("x\na b c d e f g h i j k l m n o p q r s t u v w x y " "z 1 2 3 4 5 6 7 8 9 a b c de f g h i j k l m n o p q " "r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 a b c d e fg h " "i j k", 79, 4), "x\na b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 " "8 9 a b c de\n f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 " "5 6 7 8 9 a b c d e fg\n h i j k"); BOOST_CHECK_EQUAL( FormatParagraph("This is a very long test string. This is a second " "sentence in the very long test string.", 79), "This is a very long test string. This is a second sentence in the " "very long\ntest string."); BOOST_CHECK_EQUAL( FormatParagraph("This is a very long test string.\nThis is a second " "sentence in the very long test string. This is a " "third sentence in the very long test string.", 79), "This is a very long test string.\nThis is a second sentence in the " "very long test string. This is a third\nsentence in the very long " "test string."); BOOST_CHECK_EQUAL( FormatParagraph("This is a very long test string.\n\nThis is a second " "sentence in the very long test string. This is a " "third sentence in the very long test string.", 79), "This is a very long test string.\n\nThis is a second sentence in the " "very long test string. This is a third\nsentence in the very long " "test string."); BOOST_CHECK_EQUAL( FormatParagraph( "Testing that normal newlines do not get indented.\nLike here.", 79), "Testing that normal newlines do not get indented.\nLike here."); } BOOST_AUTO_TEST_CASE(test_FormatSubVersion) { std::vector comments; comments.push_back(std::string("comment1")); std::vector comments2; comments2.push_back(std::string("comment1")); // Semicolon is discouraged but not forbidden by BIP-0014 comments2.push_back(SanitizeString( std::string("Comment2; .,_?@-; !\"#$%&'()*+/<=>[]\\^`{|}~"), SAFE_CHARS_UA_COMMENT)); BOOST_CHECK_EQUAL( FormatSubVersion("Test", 99900, std::vector()), std::string("/Test:0.9.99/")); BOOST_CHECK_EQUAL(FormatSubVersion("Test", 99900, comments), std::string("/Test:0.9.99(comment1)/")); BOOST_CHECK_EQUAL( FormatSubVersion("Test", 99900, comments2), std::string("/Test:0.9.99(comment1; Comment2; .,_?@-; )/")); } BOOST_AUTO_TEST_CASE(test_ParseFixedPoint) { int64_t amount = 0; BOOST_CHECK(ParseFixedPoint("0", 8, &amount)); BOOST_CHECK_EQUAL(amount, 0LL); BOOST_CHECK(ParseFixedPoint("1", 8, &amount)); BOOST_CHECK_EQUAL(amount, 100000000LL); BOOST_CHECK(ParseFixedPoint("0.0", 8, &amount)); BOOST_CHECK_EQUAL(amount, 0LL); BOOST_CHECK(ParseFixedPoint("-0.1", 8, &amount)); BOOST_CHECK_EQUAL(amount, -10000000LL); BOOST_CHECK(ParseFixedPoint("1.1", 8, &amount)); BOOST_CHECK_EQUAL(amount, 110000000LL); BOOST_CHECK(ParseFixedPoint("1.10000000000000000", 8, &amount)); BOOST_CHECK_EQUAL(amount, 110000000LL); BOOST_CHECK(ParseFixedPoint("1.1e1", 8, &amount)); BOOST_CHECK_EQUAL(amount, 1100000000LL); BOOST_CHECK(ParseFixedPoint("1.1e-1", 8, &amount)); BOOST_CHECK_EQUAL(amount, 11000000LL); BOOST_CHECK(ParseFixedPoint("1000", 8, &amount)); BOOST_CHECK_EQUAL(amount, 100000000000LL); BOOST_CHECK(ParseFixedPoint("-1000", 8, &amount)); BOOST_CHECK_EQUAL(amount, -100000000000LL); BOOST_CHECK(ParseFixedPoint("0.00000001", 8, &amount)); BOOST_CHECK_EQUAL(amount, 1LL); BOOST_CHECK(ParseFixedPoint("0.0000000100000000", 8, &amount)); BOOST_CHECK_EQUAL(amount, 1LL); BOOST_CHECK(ParseFixedPoint("-0.00000001", 8, &amount)); BOOST_CHECK_EQUAL(amount, -1LL); BOOST_CHECK(ParseFixedPoint("1000000000.00000001", 8, &amount)); BOOST_CHECK_EQUAL(amount, 100000000000000001LL); BOOST_CHECK(ParseFixedPoint("9999999999.99999999", 8, &amount)); BOOST_CHECK_EQUAL(amount, 999999999999999999LL); BOOST_CHECK(ParseFixedPoint("-9999999999.99999999", 8, &amount)); BOOST_CHECK_EQUAL(amount, -999999999999999999LL); BOOST_CHECK(!ParseFixedPoint("", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("a-1000", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-a1000", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-1000a", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-01000", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("00.1", 8, &amount)); BOOST_CHECK(!ParseFixedPoint(".1", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("--0.1", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("0.000000001", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-0.000000001", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("0.00000001000000001", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-10000000000.00000000", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("10000000000.00000000", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-10000000000.00000001", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("10000000000.00000001", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-10000000000.00000009", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("10000000000.00000009", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-99999999999.99999999", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("99999909999.09999999", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("92233720368.54775807", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("92233720368.54775808", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-92233720368.54775808", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-92233720368.54775809", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("1.1e", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("1.1e-", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("1.", 8, &amount)); } static void TestOtherThread(fs::path dirname, std::string lockname, bool *result) { *result = LockDirectory(dirname, lockname); } #ifndef WIN32 // Cannot do this test on WIN32 due to lack of fork() static constexpr char LockCommand = 'L'; static constexpr char UnlockCommand = 'U'; static constexpr char ExitCommand = 'X'; static void TestOtherProcess(fs::path dirname, std::string lockname, int fd) { char ch; int rv; while (true) { rv = read(fd, &ch, 1); // Wait for command assert(rv == 1); switch (ch) { case LockCommand: ch = LockDirectory(dirname, lockname); rv = write(fd, &ch, 1); assert(rv == 1); break; case UnlockCommand: ReleaseDirectoryLocks(); ch = true; // Always succeeds rv = write(fd, &ch, 1); break; case ExitCommand: close(fd); exit(0); default: assert(0); } } } #endif BOOST_AUTO_TEST_CASE(test_LockDirectory) { fs::path dirname = fs::temp_directory_path() / fs::unique_path(); const std::string lockname = ".lock"; #ifndef WIN32 // Revert SIGCHLD to default, otherwise boost.test will catch and fail on // it: there is BOOST_TEST_IGNORE_SIGCHLD but that only works when defined // at build-time of the boost library void (*old_handler)(int) = signal(SIGCHLD, SIG_DFL); // Fork another process for testing before creating the lock, so that we // won't fork while holding the lock (which might be undefined, and is not // relevant as test case as that is avoided with -daemonize). int fd[2]; BOOST_CHECK_EQUAL(socketpair(AF_UNIX, SOCK_STREAM, 0, fd), 0); pid_t pid = fork(); if (!pid) { BOOST_CHECK_EQUAL(close(fd[1]), 0); // Child: close parent end TestOtherProcess(dirname, lockname, fd[0]); } BOOST_CHECK_EQUAL(close(fd[0]), 0); // Parent: close child end #endif // Lock on non-existent directory should fail BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), false); fs::create_directories(dirname); // Probing lock on new directory should succeed BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); // Persistent lock on new directory should succeed BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), true); // Another lock on the directory from the same thread should succeed BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), true); // Another lock on the directory from a different thread within the same // process should succeed bool threadresult; std::thread thr(TestOtherThread, dirname, lockname, &threadresult); thr.join(); BOOST_CHECK_EQUAL(threadresult, true); #ifndef WIN32 // Try to aquire lock in child process while we're holding it, this should // fail. char ch; BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1); BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1); BOOST_CHECK_EQUAL((bool)ch, false); // Give up our lock ReleaseDirectoryLocks(); // Probing lock from our side now should succeed, but not hold on to the // lock. BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); // Try to acquire the lock in the child process, this should be succesful. BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1); BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1); BOOST_CHECK_EQUAL((bool)ch, true); // When we try to probe the lock now, it should fail. BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), false); // Unlock the lock in the child process BOOST_CHECK_EQUAL(write(fd[1], &UnlockCommand, 1), 1); BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1); BOOST_CHECK_EQUAL((bool)ch, true); // When we try to probe the lock now, it should succeed. BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); // Re-lock the lock in the child process, then wait for it to exit, check // successful return. After that, we check that exiting the process // has released the lock as we would expect by probing it. int processstatus; BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1); BOOST_CHECK_EQUAL(write(fd[1], &ExitCommand, 1), 1); BOOST_CHECK_EQUAL(waitpid(pid, &processstatus, 0), pid); BOOST_CHECK_EQUAL(processstatus, 0); BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); // Restore SIGCHLD signal(SIGCHLD, old_handler); BOOST_CHECK_EQUAL(close(fd[1]), 0); // Close our side of the socketpair #endif // Clean up ReleaseDirectoryLocks(); fs::remove_all(dirname); } BOOST_AUTO_TEST_CASE(test_DirIsWritable) { // Should be able to write to the system tmp dir. fs::path tmpdirname = fs::temp_directory_path(); BOOST_CHECK_EQUAL(DirIsWritable(tmpdirname), true); // Should not be able to write to a non-existent dir. tmpdirname = fs::temp_directory_path() / fs::unique_path(); BOOST_CHECK_EQUAL(DirIsWritable(tmpdirname), false); fs::create_directory(tmpdirname); // Should be able to write to it now. BOOST_CHECK_EQUAL(DirIsWritable(tmpdirname), true); fs::remove(tmpdirname); } template static void CheckConvertBits(const std::vector &in, const std::vector &expected) { std::vector outpad; bool ret = ConvertBits(outpad, in.begin(), in.end()); BOOST_CHECK(ret); BOOST_CHECK(outpad == expected); const bool dopad = (in.size() * F) % T; std::vector outnopad; ret = ConvertBits(outnopad, in.begin(), in.end()); BOOST_CHECK(ret != dopad); if (dopad) { // We should have skipped the last digit. outnopad.push_back(expected.back()); } BOOST_CHECK(outnopad == expected); // Check the other way around. std::vector orignopad; ret = ConvertBits(orignopad, expected.begin(), expected.end()); BOOST_CHECK(ret == !((expected.size() * T) % F)); BOOST_CHECK(orignopad == in); // Check with padding. We may get an extra 0 in that case. std::vector origpad; ret = ConvertBits(origpad, expected.begin(), expected.end()); BOOST_CHECK(ret); if (dopad) { BOOST_CHECK_EQUAL(origpad.back(), 0); origpad.pop_back(); } BOOST_CHECK(origpad == in); } BOOST_AUTO_TEST_CASE(test_ConvertBits) { CheckConvertBits<8, 5>({}, {}); CheckConvertBits<8, 5>({0xff}, {0x1f, 0x1c}); CheckConvertBits<8, 5>({0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x10}); CheckConvertBits<8, 5>({0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1e}); CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x18}); CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); CheckConvertBits<8, 5>({0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, {0x00, 0x04, 0x11, 0x14, 0x0a, 0x19, 0x1c, 0x09, 0x15, 0x0f, 0x06, 0x1e, 0x1e}); } +BOOST_AUTO_TEST_CASE(test_ToLower) { + BOOST_CHECK_EQUAL(ToLower('@'), '@'); + BOOST_CHECK_EQUAL(ToLower('A'), 'a'); + BOOST_CHECK_EQUAL(ToLower('Z'), 'z'); + BOOST_CHECK_EQUAL(ToLower('['), '['); + BOOST_CHECK_EQUAL(ToLower(0), 0); + BOOST_CHECK_EQUAL(ToLower(255), 255); + + std::string testVector; + Downcase(testVector); + BOOST_CHECK_EQUAL(testVector, ""); + + testVector = "#HODL"; + Downcase(testVector); + BOOST_CHECK_EQUAL(testVector, "#hodl"); + + testVector = "\x00\xfe\xff"; + Downcase(testVector); + BOOST_CHECK_EQUAL(testVector, "\x00\xfe\xff"); +} + +BOOST_AUTO_TEST_CASE(test_ToUpper) { + BOOST_CHECK_EQUAL(ToUpper('`'), '`'); + BOOST_CHECK_EQUAL(ToUpper('a'), 'A'); + BOOST_CHECK_EQUAL(ToUpper('z'), 'Z'); + BOOST_CHECK_EQUAL(ToUpper('{'), '{'); + BOOST_CHECK_EQUAL(ToUpper(0), 0); + BOOST_CHECK_EQUAL(ToUpper(255), 255); +} + +BOOST_AUTO_TEST_CASE(test_Capitalize) { + BOOST_CHECK_EQUAL(Capitalize(""), ""); + BOOST_CHECK_EQUAL(Capitalize("bitcoin"), "Bitcoin"); + BOOST_CHECK_EQUAL(Capitalize("\x00\xfe\xff"), "\x00\xfe\xff"); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/utilstrencodings.cpp b/src/utilstrencodings.cpp index 72ec0c100..4185dacaa 100644 --- a/src/utilstrencodings.cpp +++ b/src/utilstrencodings.cpp @@ -1,805 +1,817 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "utilstrencodings.h" #include "tinyformat.h" +#include #include #include #include #include static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; static const std::string SAFE_CHARS[] = { // SAFE_CHARS_DEFAULT CHARS_ALPHA_NUM + " .,;-_/:?@()", // SAFE_CHARS_UA_COMMENT CHARS_ALPHA_NUM + " .,;-_?@", // SAFE_CHARS_FILENAME CHARS_ALPHA_NUM + ".-_", }; std::string SanitizeString(const std::string &str, int rule) { std::string strResult; for (std::string::size_type i = 0; i < str.size(); i++) { if (SAFE_CHARS[rule].find(str[i]) != std::string::npos) { strResult.push_back(str[i]); } } return strResult; } const signed char p_util_hexdigit[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; signed char HexDigit(char c) { return p_util_hexdigit[(uint8_t)c]; } bool IsHex(const std::string &str) { for (std::string::const_iterator it(str.begin()); it != str.end(); ++it) { if (HexDigit(*it) < 0) { return false; } } return (str.size() > 0) && (str.size() % 2 == 0); } bool IsHexNumber(const std::string &str) { size_t starting_location = 0; if (str.size() > 2 && *str.begin() == '0' && *(str.begin() + 1) == 'x') { starting_location = 2; } for (auto c : str.substr(starting_location)) { if (HexDigit(c) < 0) { return false; } } // Return false for empty string or "0x". return (str.size() > starting_location); } std::vector ParseHex(const char *psz) { // convert hex dump to vector std::vector vch; while (true) { while (IsSpace(*psz)) { psz++; } signed char c = HexDigit(*psz++); if (c == (signed char)-1) { break; } uint8_t n = (c << 4); c = HexDigit(*psz++); if (c == (signed char)-1) { break; } n |= c; vch.push_back(n); } return vch; } std::vector ParseHex(const std::string &str) { return ParseHex(str.c_str()); } void SplitHostPort(std::string in, int &portOut, std::string &hostOut) { size_t colon = in.find_last_of(':'); // if a : is found, and it either follows a [...], or no other : is in the // string, treat it as port separator bool fHaveColon = colon != in.npos; bool fBracketed = fHaveColon && (in[0] == '[' && in[colon - 1] == ']'); // if there is a colon, and // in[0]=='[', colon is not 0, // so in[colon-1] is safe bool fMultiColon = fHaveColon && (in.find_last_of(':', colon - 1) != in.npos); if (fHaveColon && (colon == 0 || fBracketed || !fMultiColon)) { int32_t n; if (ParseInt32(in.substr(colon + 1), &n) && n > 0 && n < 0x10000) { in = in.substr(0, colon); portOut = n; } } if (in.size() > 0 && in[0] == '[' && in[in.size() - 1] == ']') { hostOut = in.substr(1, in.size() - 2); } else { hostOut = in; } } std::string EncodeBase64(const uint8_t *pch, size_t len) { static const char *pbase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string strRet = ""; strRet.reserve((len + 2) / 3 * 4); int mode = 0, left = 0; const uint8_t *pchEnd = pch + len; while (pch < pchEnd) { int enc = *(pch++); switch (mode) { case 0: // we have no bits strRet += pbase64[enc >> 2]; left = (enc & 3) << 4; mode = 1; break; case 1: // we have two bits strRet += pbase64[left | (enc >> 4)]; left = (enc & 15) << 2; mode = 2; break; case 2: // we have four bits strRet += pbase64[left | (enc >> 6)]; strRet += pbase64[enc & 63]; mode = 0; break; } } if (mode) { strRet += pbase64[left]; strRet += '='; if (mode == 1) { strRet += '='; } } return strRet; } std::string EncodeBase64(const std::string &str) { return EncodeBase64((const uint8_t *)str.c_str(), str.size()); } std::vector DecodeBase64(const char *p, bool *pfInvalid) { static const int decode64_table[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; if (pfInvalid) { *pfInvalid = false; } std::vector vchRet; vchRet.reserve(strlen(p) * 3 / 4); int mode = 0; int left = 0; while (1) { int dec = decode64_table[(uint8_t)*p]; if (dec == -1) { break; } p++; switch (mode) { case 0: // we have no bits and get 6 left = dec; mode = 1; break; case 1: // we have 6 bits and keep 4 vchRet.push_back((left << 2) | (dec >> 4)); left = dec & 15; mode = 2; break; case 2: // we have 4 bits and get 6, we keep 2 vchRet.push_back((left << 4) | (dec >> 2)); left = dec & 3; mode = 3; break; case 3: // we have 2 bits and get 6 vchRet.push_back((left << 6) | dec); mode = 0; break; } } if (pfInvalid) { switch (mode) { case 0: // 4n base64 characters processed: ok break; case 1: // 4n+1 base64 character processed: impossible *pfInvalid = true; break; case 2: // 4n+2 base64 characters processed: require '==' if (left || p[0] != '=' || p[1] != '=' || decode64_table[(uint8_t)p[2]] != -1) { *pfInvalid = true; } break; case 3: // 4n+3 base64 characters processed: require '=' if (left || p[0] != '=' || decode64_table[(uint8_t)p[1]] != -1) { *pfInvalid = true; } break; } } return vchRet; } std::string DecodeBase64(const std::string &str) { std::vector vchRet = DecodeBase64(str.c_str()); return (vchRet.size() == 0) ? std::string() : std::string((const char *)&vchRet[0], vchRet.size()); } std::string EncodeBase32(const uint8_t *pch, size_t len) { static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; std::string strRet = ""; strRet.reserve((len + 4) / 5 * 8); int mode = 0, left = 0; const uint8_t *pchEnd = pch + len; while (pch < pchEnd) { int enc = *(pch++); switch (mode) { case 0: // we have no bits strRet += pbase32[enc >> 3]; left = (enc & 7) << 2; mode = 1; break; case 1: // we have three bits strRet += pbase32[left | (enc >> 6)]; strRet += pbase32[(enc >> 1) & 31]; left = (enc & 1) << 4; mode = 2; break; case 2: // we have one bit strRet += pbase32[left | (enc >> 4)]; left = (enc & 15) << 1; mode = 3; break; case 3: // we have four bits strRet += pbase32[left | (enc >> 7)]; strRet += pbase32[(enc >> 2) & 31]; left = (enc & 3) << 3; mode = 4; break; case 4: // we have two bits strRet += pbase32[left | (enc >> 5)]; strRet += pbase32[enc & 31]; mode = 0; } } static const int nPadding[5] = {0, 6, 4, 3, 1}; if (mode) { strRet += pbase32[left]; for (int n = 0; n < nPadding[mode]; n++) { strRet += '='; } } return strRet; } std::string EncodeBase32(const std::string &str) { return EncodeBase32((const uint8_t *)str.c_str(), str.size()); } std::vector DecodeBase32(const char *p, bool *pfInvalid) { static const int decode32_table[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; if (pfInvalid) { *pfInvalid = false; } std::vector vchRet; vchRet.reserve((strlen(p)) * 5 / 8); int mode = 0; int left = 0; while (1) { int dec = decode32_table[(uint8_t)*p]; if (dec == -1) { break; } p++; switch (mode) { case 0: // we have no bits and get 5 left = dec; mode = 1; break; case 1: // we have 5 bits and keep 2 vchRet.push_back((left << 3) | (dec >> 2)); left = dec & 3; mode = 2; break; case 2: // we have 2 bits and keep 7 left = left << 5 | dec; mode = 3; break; case 3: // we have 7 bits and keep 4 vchRet.push_back((left << 1) | (dec >> 4)); left = dec & 15; mode = 4; break; case 4: // we have 4 bits, and keep 1 vchRet.push_back((left << 4) | (dec >> 1)); left = dec & 1; mode = 5; break; case 5: // we have 1 bit, and keep 6 left = left << 5 | dec; mode = 6; break; case 6: // we have 6 bits, and keep 3 vchRet.push_back((left << 2) | (dec >> 3)); left = dec & 7; mode = 7; break; case 7: // we have 3 bits, and keep 0 vchRet.push_back((left << 5) | dec); mode = 0; break; } } if (pfInvalid) switch (mode) { case 0: // 8n base32 characters processed: ok break; case 1: // 8n+1 base32 characters processed: impossible case 3: // +3 case 6: // +6 *pfInvalid = true; break; case 2: // 8n+2 base32 characters processed: require '======' if (left || p[0] != '=' || p[1] != '=' || p[2] != '=' || p[3] != '=' || p[4] != '=' || p[5] != '=' || decode32_table[(uint8_t)p[6]] != -1) { *pfInvalid = true; } break; case 4: // 8n+4 base32 characters processed: require '====' if (left || p[0] != '=' || p[1] != '=' || p[2] != '=' || p[3] != '=' || decode32_table[(uint8_t)p[4]] != -1) { *pfInvalid = true; } break; case 5: // 8n+5 base32 characters processed: require '===' if (left || p[0] != '=' || p[1] != '=' || p[2] != '=' || decode32_table[(uint8_t)p[3]] != -1) { *pfInvalid = true; } break; case 7: // 8n+7 base32 characters processed: require '=' if (left || p[0] != '=' || decode32_table[(uint8_t)p[1]] != -1) { *pfInvalid = true; } break; } return vchRet; } std::string DecodeBase32(const std::string &str) { std::vector vchRet = DecodeBase32(str.c_str()); return (vchRet.size() == 0) ? std::string() : std::string((const char *)&vchRet[0], vchRet.size()); } static bool ParsePrechecks(const std::string &str) { // No empty string allowed if (str.empty()) { return false; } // No padding allowed if (str.size() >= 1 && (IsSpace(str[0]) || IsSpace(str[str.size() - 1]))) { return false; } // No embedded NUL characters allowed if (str.size() != strlen(str.c_str())) { return false; } return true; } bool ParseInt32(const std::string &str, int32_t *out) { if (!ParsePrechecks(str)) { return false; } char *endp = nullptr; // strtol will not set errno if valid errno = 0; long int n = strtol(str.c_str(), &endp, 10); if (out) { *out = (int32_t)n; } // Note that strtol returns a *long int*, so even if strtol doesn't report a // over/underflow we still have to check that the returned value is within // the range of an *int32_t*. On 64-bit platforms the size of these types // may be different. return endp && *endp == 0 && !errno && n >= std::numeric_limits::min() && n <= std::numeric_limits::max(); } bool ParseInt64(const std::string &str, int64_t *out) { if (!ParsePrechecks(str)) { return false; } char *endp = nullptr; // strtoll will not set errno if valid errno = 0; long long int n = strtoll(str.c_str(), &endp, 10); if (out) { *out = (int64_t)n; } // Note that strtoll returns a *long long int*, so even if strtol doesn't // report a over/underflow we still have to check that the returned value is // within the range of an *int64_t*. return endp && *endp == 0 && !errno && n >= std::numeric_limits::min() && n <= std::numeric_limits::max(); } bool ParseUInt32(const std::string &str, uint32_t *out) { if (!ParsePrechecks(str)) { return false; } // Reject negative values, unfortunately strtoul accepts these by default if // they fit in the range if (str.size() >= 1 && str[0] == '-') { return false; } char *endp = nullptr; // strtoul will not set errno if valid errno = 0; unsigned long int n = strtoul(str.c_str(), &endp, 10); if (out) { *out = (uint32_t)n; } // Note that strtoul returns a *unsigned long int*, so even if it doesn't // report a over/underflow we still have to check that the returned value is // within the range of an *uint32_t*. On 64-bit platforms the size of these // types may be different. return endp && *endp == 0 && !errno && n <= std::numeric_limits::max(); } bool ParseUInt64(const std::string &str, uint64_t *out) { if (!ParsePrechecks(str)) { return false; } // Reject negative values, unfortunately strtoull accepts these by default // if they fit in the range if (str.size() >= 1 && str[0] == '-') { return false; } char *endp = nullptr; // strtoull will not set errno if valid errno = 0; unsigned long long int n = strtoull(str.c_str(), &endp, 10); if (out) { *out = (uint64_t)n; } // Note that strtoull returns a *unsigned long long int*, so even if it // doesn't report a over/underflow we still have to check that the returned // value is within the range of an *uint64_t*. return endp && *endp == 0 && !errno && n <= std::numeric_limits::max(); } bool ParseDouble(const std::string &str, double *out) { if (!ParsePrechecks(str)) { return false; } // No hexadecimal floats allowed if (str.size() >= 2 && str[0] == '0' && str[1] == 'x') { return false; } std::istringstream text(str); text.imbue(std::locale::classic()); double result; text >> result; if (out) { *out = result; } return text.eof() && !text.fail(); } std::string FormatParagraph(const std::string &in, size_t width, size_t indent) { std::stringstream out; size_t ptr = 0; size_t indented = 0; while (ptr < in.size()) { size_t lineend = in.find_first_of('\n', ptr); if (lineend == std::string::npos) { lineend = in.size(); } const size_t linelen = lineend - ptr; const size_t rem_width = width - indented; if (linelen <= rem_width) { out << in.substr(ptr, linelen + 1); ptr = lineend + 1; indented = 0; } else { size_t finalspace = in.find_last_of(" \n", ptr + rem_width); if (finalspace == std::string::npos || finalspace < ptr) { // No place to break; just include the entire word and move on finalspace = in.find_first_of("\n ", ptr); if (finalspace == std::string::npos) { // End of the string, just add it and break out << in.substr(ptr); break; } } out << in.substr(ptr, finalspace - ptr) << "\n"; if (in[finalspace] == '\n') { indented = 0; } else if (indent) { out << std::string(indent, ' '); indented = indent; } ptr = finalspace + 1; } } return out.str(); } std::string i64tostr(int64_t n) { return strprintf("%d", n); } std::string itostr(int n) { return strprintf("%d", n); } int64_t atoi64(const char *psz) { #ifdef _MSC_VER return _atoi64(psz); #else return strtoll(psz, nullptr, 10); #endif } int64_t atoi64(const std::string &str) { #ifdef _MSC_VER return _atoi64(str.c_str()); #else return strtoll(str.c_str(), nullptr, 10); #endif } int atoi(const std::string &str) { return atoi(str.c_str()); } /** * Upper bound for mantissa. * 10^18-1 is the largest arbitrary decimal that will fit in a signed 64-bit * integer. Larger integers cannot consist of arbitrary combinations of 0-9: * * 999999999999999999 1^18-1 * 9223372036854775807 (1<<63)-1 (max int64_t) * 9999999999999999999 1^19-1 (would overflow) */ static const int64_t UPPER_BOUND = 1000000000000000000LL - 1LL; /** Helper function for ParseFixedPoint */ static inline bool ProcessMantissaDigit(char ch, int64_t &mantissa, int &mantissa_tzeros) { if (ch == '0') { ++mantissa_tzeros; } else { for (int i = 0; i <= mantissa_tzeros; ++i) { // overflow if (mantissa > (UPPER_BOUND / 10LL)) { return false; } mantissa *= 10; } mantissa += ch - '0'; mantissa_tzeros = 0; } return true; } bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) { int64_t mantissa = 0; int64_t exponent = 0; int mantissa_tzeros = 0; bool mantissa_sign = false; bool exponent_sign = false; int ptr = 0; int end = val.size(); int point_ofs = 0; if (ptr < end && val[ptr] == '-') { mantissa_sign = true; ++ptr; } if (ptr < end) { if (val[ptr] == '0') { // pass single 0 ++ptr; } else if (val[ptr] >= '1' && val[ptr] <= '9') { while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') { if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros)) { // overflow return false; } ++ptr; } } else { // missing expected digit return false; } } else { // empty string or loose '-' return false; } if (ptr < end && val[ptr] == '.') { ++ptr; if (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') { while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') { if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros)) { // overflow return false; } ++ptr; ++point_ofs; } } else { // missing expected digit return false; } } if (ptr < end && (val[ptr] == 'e' || val[ptr] == 'E')) { ++ptr; if (ptr < end && val[ptr] == '+') { ++ptr; } else if (ptr < end && val[ptr] == '-') { exponent_sign = true; ++ptr; } if (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') { while (ptr < end && val[ptr] >= '0' && val[ptr] <= '9') { if (exponent > (UPPER_BOUND / 10LL)) { // overflow return false; } exponent = exponent * 10 + val[ptr] - '0'; ++ptr; } } else { // missing expected digit return false; } } if (ptr != end) { // trailing garbage return false; } // finalize exponent if (exponent_sign) { exponent = -exponent; } exponent = exponent - point_ofs + mantissa_tzeros; // finalize mantissa if (mantissa_sign) { mantissa = -mantissa; } // convert to one 64-bit fixed-point value exponent += decimals; if (exponent < 0) { // cannot represent values smaller than 10^-decimals return false; } if (exponent >= 18) { // cannot represent values larger than or equal to 10^(18-decimals) return false; } for (int i = 0; i < exponent; ++i) { if (mantissa > (UPPER_BOUND / 10LL) || mantissa < -(UPPER_BOUND / 10LL)) { // overflow return false; } mantissa *= 10; } if (mantissa > UPPER_BOUND || mantissa < -UPPER_BOUND) { // overflow return false; } if (amount_out) { *amount_out = mantissa; } return true; } + +void Downcase(std::string &str) { + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return ToLower(c); }); +} + +std::string Capitalize(std::string str) { + if (str.empty()) return str; + str[0] = ToUpper(str.front()); + return str; +} diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index 774c9083b..f9398489f 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -1,208 +1,250 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. /** * Utilities for converting data from/to strings. */ #ifndef BITCOIN_UTILSTRENCODINGS_H #define BITCOIN_UTILSTRENCODINGS_H #include #include #include #define BEGIN(a) ((char *)&(a)) #define END(a) ((char *)&((&(a))[1])) #define UBEGIN(a) ((uint8_t *)&(a)) #define UEND(a) ((uint8_t *)&((&(a))[1])) #define ARRAYLEN(array) (sizeof(array) / sizeof((array)[0])) /** Used by SanitizeString() */ enum SafeChars { //!< The full set of allowed chars SAFE_CHARS_DEFAULT, //!< BIP-0014 subset SAFE_CHARS_UA_COMMENT, //!< Chars allowed in filenames SAFE_CHARS_FILENAME, }; /** * Remove unsafe chars. Safe chars chosen to allow simple messages/URLs/email * addresses, but avoid anything even possibly remotely dangerous like & or > * @param[in] str The string to sanitize * @param[in] rule The set of safe chars to choose (default: least * restrictive) * @return A new string without unsafe chars */ std::string SanitizeString(const std::string &str, int rule = SAFE_CHARS_DEFAULT); std::vector ParseHex(const char *psz); std::vector ParseHex(const std::string &str); signed char HexDigit(char c); /** * Returns true if each character in str is a hex character, and has an even * number of hex digits. */ bool IsHex(const std::string &str); /** * Return true if the string is a hex number, optionally prefixed with "0x" */ bool IsHexNumber(const std::string &str); std::vector DecodeBase64(const char *p, bool *pfInvalid = nullptr); std::string DecodeBase64(const std::string &str); std::string EncodeBase64(const uint8_t *pch, size_t len); std::string EncodeBase64(const std::string &str); std::vector DecodeBase32(const char *p, bool *pfInvalid = nullptr); std::string DecodeBase32(const std::string &str); std::string EncodeBase32(const uint8_t *pch, size_t len); std::string EncodeBase32(const std::string &str); void SplitHostPort(std::string in, int &portOut, std::string &hostOut); std::string i64tostr(int64_t n); std::string itostr(int n); int64_t atoi64(const char *psz); int64_t atoi64(const std::string &str); int atoi(const std::string &str); /** * Tests if the given character is a whitespace character. The whitespace * characters are: space, form-feed ('\f'), newline ('\n'), carriage return * ('\r'), horizontal tab ('\t'), and vertical tab ('\v'). * * This function is locale independent. Under the C locale this function gives * the same result as std::isspace. * * @param[in] c character to test * @return true if the argument is a whitespace character; otherwise * false */ constexpr inline bool IsSpace(char c) noexcept { return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'; } /** * Convert string to signed 32-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, false if * not the entire string could be parsed or when overflow or underflow occurred. */ bool ParseInt32(const std::string &str, int32_t *out); /** * Convert string to signed 64-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, false if * not the entire string could be parsed or when overflow or underflow occurred. */ bool ParseInt64(const std::string &str, int64_t *out); /** * Convert decimal string to unsigned 32-bit integer with strict parse error * feedback. * @returns true if the entire string could be parsed as valid integer, false if * not the entire string could be parsed or when overflow or underflow occurred. */ bool ParseUInt32(const std::string &str, uint32_t *out); /** * Convert decimal string to unsigned 64-bit integer with strict parse error * feedback. * @returns true if the entire string could be parsed as valid integer, false if * not the entire string could be parsed or when overflow or underflow occurred. */ bool ParseUInt64(const std::string &str, uint64_t *out); /** * Convert string to double with strict parse error feedback. * @returns true if the entire string could be parsed as valid double, false if * not the entire string could be parsed or when overflow or underflow occurred. */ bool ParseDouble(const std::string &str, double *out); template std::string HexStr(const T itbegin, const T itend, bool fSpaces = false) { std::string rv; static const char hexmap[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; rv.reserve((itend - itbegin) * 3); for (T it = itbegin; it < itend; ++it) { uint8_t val = uint8_t(*it); if (fSpaces && it != itbegin) rv.push_back(' '); rv.push_back(hexmap[val >> 4]); rv.push_back(hexmap[val & 15]); } return rv; } template inline std::string HexStr(const T &vch, bool fSpaces = false) { return HexStr(vch.begin(), vch.end(), fSpaces); } /** * Format a paragraph of text to a fixed width, adding spaces for indentation to * any added line. */ std::string FormatParagraph(const std::string &in, size_t width = 79, size_t indent = 0); /** * Timing-attack-resistant comparison. * Takes time proportional to length of first argument. */ template bool TimingResistantEqual(const T &a, const T &b) { if (b.size() == 0) return a.size() == 0; size_t accumulator = a.size() ^ b.size(); for (size_t i = 0; i < a.size(); i++) accumulator |= a[i] ^ b[i % b.size()]; return accumulator == 0; } /** * Parse number as fixed point according to JSON number syntax. * See http://json.org/number.gif * @returns true on success, false on error. * @note The result must be in the range (-10^18,10^18), otherwise an overflow * error will trigger. */ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); /** * Convert from one power-of-2 number base to another. * * If padding is enabled, this always return true. If not, then it returns true * of all the bits of the input are encoded in the output. */ template bool ConvertBits(O &out, I it, I end) { size_t acc = 0; size_t bits = 0; constexpr size_t maxv = (1 << tobits) - 1; constexpr size_t max_acc = (1 << (frombits + tobits - 1)) - 1; while (it != end) { acc = ((acc << frombits) | *it) & max_acc; bits += frombits; while (bits >= tobits) { bits -= tobits; out.push_back((acc >> bits) & maxv); } ++it; } // We have remaining bits to encode but do not pad. if (!pad && bits) { return false; } // We have remaining bits to encode so we do pad. if (pad && bits) { out.push_back((acc << (tobits - bits)) & maxv); } return true; } +/** + * Converts the given character to its lowercase equivalent. + * This function is locale independent. It only converts uppercase + * characters in the standard 7-bit ASCII range. + * @param[in] c the character to convert to lowercase. + * @return the lowercase equivalent of c; or the argument + * if no conversion is possible. + */ +constexpr unsigned char ToLower(unsigned char c) { + return (c >= 'A' && c <= 'Z' ? (c - 'A') + 'a' : c); +} + +/** + * Converts the given string to its lowercase equivalent. + * This function is locale independent. It only converts uppercase + * characters in the standard 7-bit ASCII range. + * @param[in,out] str the string to convert to lowercase. + */ +void Downcase(std::string &str); + +/** + * Converts the given character to its uppercase equivalent. + * This function is locale independent. It only converts lowercase + * characters in the standard 7-bit ASCII range. + * @param[in] c the character to convert to uppercase. + * @return the uppercase equivalent of c; or the argument + * if no conversion is possible. + */ +constexpr unsigned char ToUpper(unsigned char c) { + return (c >= 'a' && c <= 'z' ? (c - 'a') + 'A' : c); +} + +/** + * Capitalizes the first character of the given string. + * This function is locale independent. It only capitalizes the + * first character of the argument if it has an uppercase equivalent + * in the standard 7-bit ASCII range. + * @param[in] str the string to capitalize. + * @return string with the first letter capitalized. + */ +std::string Capitalize(std::string str); + #endif // BITCOIN_UTILSTRENCODINGS_H diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh index c99370c68..bfb4832bf 100755 --- a/test/lint/lint-locale-dependence.sh +++ b/test/lint/lint-locale-dependence.sh @@ -1,223 +1,221 @@ #!/usr/bin/env bash KNOWN_VIOLATIONS=( "src/bitcoin-tx.cpp.*stoul" "src/bitcoin-tx.cpp.*trim_right" "src/bitcoin-tx.cpp:.*atoi" "src/core_read.cpp.*is_digit" "src/dbwrapper.cpp:.*vsnprintf" "src/httprpc.cpp.*trim" "src/init.cpp:.*atoi" - "src/netbase.cpp.*to_lower" "src/qt/rpcconsole.cpp:.*atoi" "src/qt/rpcconsole.cpp:.*isdigit" "src/rest.cpp:.*strtol" - "src/rpc/server.cpp.*to_upper" "src/test/dbwrapper_tests.cpp:.*snprintf" "src/test/getarg_tests.cpp.*split" "src/torcontrol.cpp:.*atoi" "src/uint256.cpp:.*tolower" "src/util.cpp:.*atoi" "src/util.cpp:.*tolower" "src/utilmoneystr.cpp:.*isdigit" "src/utilstrencodings.cpp:.*atoi" # Append the opening parenthesis to avoid shadowing strtoll with grep "src/utilstrencodings.cpp:.*strtol\(" "src/utilstrencodings.cpp:.*strtoll" # Append the opening parenthesis to avoid shadowing strtoull with grep "src/utilstrencodings.cpp:.*strtoul\(" "src/utilstrencodings.cpp:.*strtoull" "src/utilstrencodings.h:.*atoi" ) REGEXP_IGNORE_EXTERNAL_DEPENDENCIES="src/(crypto/ctaes/|leveldb/|secp256k1/|seeder/|tinyformat.h|univalue/)" LOCALE_DEPENDENT_FUNCTIONS=( alphasort # LC_COLLATE (via strcoll) asctime # LC_TIME (directly) asprintf # (via vasprintf) atof # LC_NUMERIC (via strtod) atoi # LC_NUMERIC (via strtol) atol # LC_NUMERIC (via strtol) atoll # (via strtoll) atoq btowc # LC_CTYPE (directly) ctime # (via asctime or localtime) dprintf # (via vdprintf) fgetwc fgetws fold_case # boost::locale::fold_case # fprintf # (via vfprintf) fputwc fputws fscanf # (via __vfscanf) fwprintf # (via __vfwprintf) getdate # via __getdate_r => isspace // __localtime_r getwc getwchar is_digit # boost::algorithm::is_digit is_space # boost::algorithm::is_space isalnum # LC_CTYPE isalpha # LC_CTYPE isblank # LC_CTYPE iscntrl # LC_CTYPE isctype # LC_CTYPE isdigit # LC_CTYPE isgraph # LC_CTYPE islower # LC_CTYPE isprint # LC_CTYPE ispunct # LC_CTYPE isspace # LC_CTYPE isupper # LC_CTYPE iswalnum # LC_CTYPE iswalpha # LC_CTYPE iswblank # LC_CTYPE iswcntrl # LC_CTYPE iswctype # LC_CTYPE iswdigit # LC_CTYPE iswgraph # LC_CTYPE iswlower # LC_CTYPE iswprint # LC_CTYPE iswpunct # LC_CTYPE iswspace # LC_CTYPE iswupper # LC_CTYPE iswxdigit # LC_CTYPE isxdigit # LC_CTYPE localeconv # LC_NUMERIC + LC_MONETARY mblen # LC_CTYPE mbrlen mbrtowc mbsinit mbsnrtowcs mbsrtowcs mbstowcs # LC_CTYPE mbtowc # LC_CTYPE mktime normalize # boost::locale::normalize # printf # LC_NUMERIC putwc putwchar scanf # LC_NUMERIC setlocale snprintf sprintf sscanf stod stof stoi stol stold stoll stoul stoull strcasecmp strcasestr strcoll # LC_COLLATE # strerror strfmon strftime # LC_TIME strncasecmp strptime strtod # LC_NUMERIC strtof strtoimax strtol # LC_NUMERIC strtold strtoll strtoq strtoul # LC_NUMERIC strtoull strtoumax strtouq strxfrm # LC_COLLATE swprintf to_lower # boost::locale::to_lower to_title # boost::locale::to_title to_upper # boost::locale::to_upper tolower # LC_CTYPE toupper # LC_CTYPE towctrans towlower # LC_CTYPE towupper # LC_CTYPE trim # boost::algorithm::trim trim_left # boost::algorithm::trim_left trim_right # boost::algorithm::trim_right ungetwc vasprintf vdprintf versionsort vfprintf vfscanf vfwprintf vprintf vscanf vsnprintf vsprintf vsscanf vswprintf vwprintf wcrtomb wcscasecmp wcscoll # LC_COLLATE wcsftime # LC_TIME wcsncasecmp wcsnrtombs wcsrtombs wcstod # LC_NUMERIC wcstof wcstoimax wcstol # LC_NUMERIC wcstold wcstoll wcstombs # LC_CTYPE wcstoul # LC_NUMERIC wcstoull wcstoumax wcswidth wcsxfrm # LC_COLLATE wctob wctomb # LC_CTYPE wctrans wctype wcwidth wprintf ) function join_array { local IFS="$1" shift echo "$*" } REGEXP_IGNORE_KNOWN_VIOLATIONS=$(join_array "|" "${KNOWN_VIOLATIONS[@]}") # Invoke "git grep" only once in order to minimize run-time REGEXP_LOCALE_DEPENDENT_FUNCTIONS=$(join_array "|" "${LOCALE_DEPENDENT_FUNCTIONS[@]}") GIT_GREP_OUTPUT=$(git grep -nE "[^a-zA-Z0-9_\`'\"<>](${REGEXP_LOCALE_DEPENDENT_FUNCTIONS}(_r|_s)?)[^a-zA-Z0-9_\`'\"<>]" -- ":/*.cpp" ":/*.h") EXIT_CODE=0 for LOCALE_DEPENDENT_FUNCTION in "${LOCALE_DEPENDENT_FUNCTIONS[@]}"; do MATCHES=$(grep -E "[^a-zA-Z0-9_\`'\"<>]${LOCALE_DEPENDENT_FUNCTION}(_r|_s)?[^a-zA-Z0-9_\`'\"<>]" <<< "${GIT_GREP_OUTPUT}" | \ grep -vE "\.(c|cpp|h):[0-9]+:\s*(//|\*|/\*|\").*${LOCALE_DEPENDENT_FUNCTION}") if [[ ${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES} != "" ]]; then MATCHES=$(grep -vE "${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES}" <<< "${MATCHES}") fi if [[ ${REGEXP_IGNORE_KNOWN_VIOLATIONS} != "" ]]; then MATCHES=$(grep -vE "${REGEXP_IGNORE_KNOWN_VIOLATIONS}" <<< "${MATCHES}") fi if [[ ${MATCHES} != "" ]]; then echo "The locale dependent function ${LOCALE_DEPENDENT_FUNCTION}(...) appears to be used:" echo "${MATCHES}" echo EXIT_CODE=1 fi done if [[ ${EXIT_CODE} != 0 ]]; then echo "Unnecessary locale dependence can cause bugs that are very" echo "tricky to isolate and fix. Please avoid using locale dependent" echo "functions if possible." echo echo "Advice not applicable in this specific case? Add an exception" echo "by updating the ignore list in $0" fi exit ${EXIT_CODE}