// Copyright (c) 2016-2026 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
//
// Regression coverage for the b3e81f1ed fix:
//   "stratum: do not abort the daemon on a malformed 63-character job_id"
//
// The EWBF "31 bytes job_id" path in stratum_mining_submit() completes a
// 63-character job_id with each hex digit in turn and feeds the result to
// uint256(). ParseHex() stops at the first non-hex character and returns a
// SHORT vector without signalling an error, and base_blob's vector ctor
// asserts vch.size() == 32. asserts are live in release builds here, and the
// job_id arrives from an unauthenticated client -- so a single mining.submit
// whose 63-character job_id contains any non-hex byte (63 spaces will do)
// used to abort the node.
//
// The fix size-checks each candidate before constructing uint256. These tests
// pin the exact invariant that guard relies on, using the real ParseHex and
// uint256 primitives, without ever constructing a uint256 from a short vector
// (which would still abort under the guard we are protecting).

#include <gtest/gtest.h>
#include <string>
#include <vector>

#include "uint256.h"
#include "utilstrencodings.h"

namespace {

const std::string HEXDIGITS = "0123456789abcdef";

// Mirrors the guarded completion loop in stratum_mining_submit(): try every
// single-hex-digit completion and report whether ANY of them parses to a
// whole 32-byte value. Only then is uint256() construction reached.
bool AnyCompletionParsesTo32(const std::string& jobid) {
    for (char d : HEXDIGITS) {
        std::vector<unsigned char> vch = ParseHex(jobid + d);
        if (vch.size() == 32) return true;
    }
    return false;
}

} // namespace

// The exact example from the fix commit: a 63-character job_id of spaces.
// No completion may reach a 32-byte vector, so the daemon never constructs
// uint256() and never aborts.
TEST(StratumJobId, AllSpacesNeverYields32Bytes) {
    std::string spaces(63, ' ');
    ASSERT_EQ(spaces.size(), 63u);
    for (char d : HEXDIGITS) {
        EXPECT_NE(ParseHex(spaces + d).size(), 32u)
            << "completion '" << d << "' unexpectedly produced 32 bytes";
    }
    EXPECT_FALSE(AnyCompletionParsesTo32(spaces));
}

// A single non-hex byte embedded in an otherwise-hex 63-char job_id is enough:
// ParseHex stops at it, so every completion is short.
TEST(StratumJobId, SingleNonHexByteInMiddleIsRejected) {
    std::string jobid(63, 'a');
    jobid[30] = 'g'; // 'g' is not a hex digit
    ASSERT_EQ(jobid.size(), 63u);
    EXPECT_FALSE(AnyCompletionParsesTo32(jobid));
}

// A non-hex byte at the very end (position 62) is likewise rejected: the last
// hex pair can never complete to a whole byte.
TEST(StratumJobId, NonHexByteAtEndIsRejected) {
    std::string jobid(62, 'a');
    jobid.push_back('z'); // length 63, last char non-hex
    ASSERT_EQ(jobid.size(), 63u);
    EXPECT_FALSE(AnyCompletionParsesTo32(jobid));
}

// A genuine truncated EWBF job_id -- 63 real hex characters -- must complete
// to exactly 32 bytes for every digit, so uint256() construction is safe.
TEST(StratumJobId, ValidSixtyThreeHexCompletesToExactly32Bytes) {
    std::string jobid(63, 'a');
    ASSERT_EQ(jobid.size(), 63u);
    for (char d : HEXDIGITS) {
        std::vector<unsigned char> vch = ParseHex(jobid + d);
        ASSERT_EQ(vch.size(), 32u) << "completion '" << d << "'";
        // 32-byte vector: construction must not trip the size assertion.
        uint256 h(vch);
        EXPECT_EQ(h.size(), 32u);
    }
    EXPECT_TRUE(AnyCompletionParsesTo32(jobid));
}

// Sanity anchors for ParseHex's odd/even handling that the loop depends on:
// an odd hex length drops the trailing nibble (63 hex -> 31 bytes), and one
// more hex char fills the 32nd byte (64 hex -> 32 bytes).
TEST(StratumJobId, ParseHexOddLengthDropsTrailingNibble) {
    EXPECT_EQ(ParseHex(std::string(63, 'a')).size(), 31u);
    EXPECT_EQ(ParseHex(std::string(64, 'a')).size(), 32u);
}
