// net_text_codec_test — §1.10 fixed-length wire text: the client keeps UTF-8 // internally, while the Chinese 40250 wire uses GB2312-compatible bytes. #include "../src/net/text_codec.h" #include #include #include static int g_fail = 0; #define CHECK(c, msg) \ do { \ if (!(c)) { \ std::fprintf(stderr, "FAIL: %s\n", msg); \ ++g_fail; \ } \ } while (0) // U+6C49 "汉": 3 bytes in UTF-8, 2 bytes in GB2312 (BA BA). static std::string cjk(int n) { std::string s; for (int i = 0; i < n; ++i) { s += "\xE6\xB1\x89"; } return s; } int main() { using mtnet::from_wire_str; using mtnet::to_wire; // CHARACTER_NAME_MAX_LEN is a 24-byte wire capacity. Twelve Chinese // characters fit; measuring the UTF-8 input would incorrectly allow eight. { const std::string in = cjk(12); CHECK(in.size() == 36, "12 CJK == 36 UTF-8 bytes"); const std::string w = to_wire(in, 24); CHECK(w.size() == 24, "12 CJK == 24 GB2312 bytes"); CHECK(from_wire_str(w.data(), w.size()) == in, "12 CJK round-trip through GB2312"); CHECK(mtnet::wire_text_fits(in, 24), "12 CJK fits 24-byte name field"); } // Thirteen characters need 26 GB2312 bytes. Conversion truncates only at a // legacy character boundary, while input validation rejects the overlong name. { const std::string in = cjk(13); CHECK(in.size() == 39, "13 CJK == 39 UTF-8 bytes"); const std::string w = to_wire(in, 24); CHECK(w.size() == 24, "13 CJK is capped at 24 wire bytes"); CHECK(from_wire_str(w.data(), w.size()) == cjk(12), "cap keeps 12 whole CJK chars"); CHECK(!mtnet::wire_text_fits(in, 24), "13 CJK rejected by 24-byte name field"); } // The preflight used by M2Client must use the selected protocol's rule. // A 32-byte 40250 shop-sign field therefore accepts sixteen GB2312 CJK // characters even though the UTF-8 input occupies 48 bytes. The generic // transport intentionally keeps its own byte contract. { CHECK(mtnet::protocol_text_fits(cjk(16), 32, true), "classic preflight measures the encoded 32-byte sign"); CHECK(!mtnet::protocol_text_fits(cjk(17), 32, true), "classic preflight rejects a 34-byte sign"); CHECK(!mtnet::protocol_text_fits(cjk(16), 32, false), "generic preflight keeps its UTF-8 byte contract"); } // Invalid UTF-8 is not leaked to the legacy wire and is rejected by the // strict validation helper. { std::string in = cjk(12); in += '\xE6'; // dangling UTF-8 lead byte CHECK(to_wire(in, 24).size() == 24, "invalid suffix cannot overflow cap"); CHECK(!mtnet::wire_text_fits(in, 24), "invalid UTF-8 rejected"); } // ASCII remains one byte per character and round-trips unchanged. { const std::string in = "Warrior"; CHECK(to_wire(in, 24) == in, "ascii under cap unchanged"); CHECK(to_wire(std::string(40, 'x'), 24).size() == 24, "ascii over cap -> 24"); CHECK(from_wire_str(in.data(), in.size()) == in, "ascii round-trip"); } // The user-visible UTF-8 text is not limited to the test character 汉. A // real Chinese name must round-trip through the 40250 GB2312 boundary too. { const std::string in = "龙驹简体中文"; const std::string wire = to_wire(in, 24); CHECK(wire.size() == 12, "Chinese name uses GB2312 bytes, not UTF-8 bytes"); CHECK(from_wire_str(wire.data(), wire.size()) == in, "Chinese name round-trips through the 40250 wire code page"); } { const std::string in = "龙驹简体中文免费版"; const std::string wire = to_wire(in, 24); CHECK(in.size() == 27, "target Chinese name is 27 UTF-8 bytes"); CHECK(wire.size() == 18 && mtnet::wire_text_fits(in, 24), "target Chinese name fits the 24-byte GB2312 field"); CHECK(from_wire_str(wire.data(), wire.size()) == in, "target Chinese name round-trips through the 40250 wire code page"); } // Decode legacy bytes up to NUL. { char buf[25] = {}; const std::string legacy = "\xBA\xBA\xBA\xBA\xBA\xBA"; std::memcpy(buf, legacy.data(), legacy.size()); CHECK(from_wire_str(buf, sizeof(buf) - 1) == cjk(3), "decodes GB2312 up to NUL"); } { char buf[25] = {}; const std::string legacy = to_wire(cjk(12), 24); std::memcpy(buf, legacy.data(), legacy.size()); buf[24] = '\xBA'; // dangling lead byte at the field boundary CHECK(from_wire_str(buf, 25) == cjk(12), "dangling legacy lead byte is ignored"); } { // Full 24-byte field, no NUL terminator: read all twelve characters. char buf[24]; const std::string legacy = to_wire(cjk(12), 24); std::memcpy(buf, legacy.data(), legacy.size()); CHECK(from_wire_str(buf, 24) == cjk(12), "unterminated full field read whole"); } if (g_fail) { std::fprintf(stderr, "%d checks FAILED\n", g_fail); return 1; } std::puts("net_text_codec_test: OK"); return 0; }