2V1-a: port the EterLib network layer and the Crypto++ packet cipher
NetStream/NetAddress/NetDevice/NetPacketHeaderMap and EterBase/cipher.cpp are mechanical copies. The winsock shim maps Winsock onto BSD sockets and keeps the Winsock semantics CNetworkStream relies on: select ignores nfds, a socket whose connect failed is not writable (40250 times out instead), EINPROGRESS reads as WSAEWOULDBLOCK, and SIGPIPE is ignored. port.net covers loopback connect, send/recv, peer close, the refused connect timeout and the client/server key agreement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
afe63bfa61
commit
cd3b8b506f
@@ -507,3 +507,4 @@
|
||||
{"time": "2026-09-23T03:27:45Z", "event": "port_round", "unit": "PORT-PLAN 2V0-f: Godot window input and Canvas UI command adapter (partial)", "ported": [], "adapted": ["40250 CWindowManager mouse/key/focus/update bridge into Godot", "CGraphicScreen Bar/Line viewport clip and CGraphicTextInstance text render commands", "CGraphicImage and CGraphicImageInstance image command generation; CPythonResource registration; TGA/DDS/PNG dimensions", "Godot Canvas Control renders native commands and decodes TGA images from 40250 pack", "CGraphicSubImage .sub registration, referenced image dimensions and crop bounds", "CGraphicExpandedImageInstance allocation, type identity and lifetime for script bindings"], "divergent": ["CPythonApplication::Create and Loop are still platform stubs: real system.py cannot show Logo/Popup", "Text metrics, fonts, vertical clipping and non-TGA packed image variants are not verified at parity", "ExpandedImage Canvas rendering still omits original rotation, scale, rendering rectangle and blend mode", "Exploratory monkeypatch past app.Create reaches logo setup but stopping that second RunApp crashes during Python finalization; lifecycle ownership remains unresolved"], "needs_live": [], "tests": ["cmake --build build --target mtgodot -j6: pass", "godot --headless --path project --script python_host_test.gd: pass input/focus/update, bar, clip, text, packed TGA texture, .sub and ExpandedImage create/delete", "ctest --test-dir build --output-on-failure -j6: 29/29 pass, one skipped", "git diff --check: pass"], "not_done": "2V0-f application lifecycle adapter and real visible interactive Logo/Popup; normal app route remains legacy and 2V0 acceptance gate is open", "status_note": "No audit/port-map entry changed. The skill mentions scripts/port_map.py, but that file is absent from this checkout; no port-map check was run."}
|
||||
{"time": "2026-09-23T04:35:58Z", "event": "port_round", "unit": "PORT-PLAN 2V0-f: real system.py app loop in Godot (complete)", "ported": ["CGraphicExpandedImageInstance::OnRender and SetRenderingRect (40250 verbatim)", "CGraphicImageInstance::OnRender (40250 verbatim)", "CGraphicBase::GetColor / CPythonGraphic::GenerateColor"], "adapted": ["script fiber runs system.py app.Loop; UIUpdate = one CPythonApplication::Process", "image render commands carry base texture name + quad + UV (D3D -0.5 offset compensated)", "PythonBoot Stop clears the launcher before window manager teardown like 40250 Main", "MT_TEST_MODE=python_ui visible mode"], "tests": ["port.app_loop", "python_host_test.gd"], "todo": ["fnt/CGraphicText font pipeline and text alignment", "ExpandedImage blend modes"]}
|
||||
{"time": "2026-09-23T05:15:20Z", "event": "port_round", "unit": "PORT-PLAN 2V0-g: .fnt font pipeline (CGraphicText/CGraphicFontTexture/CGraphicDib/CGraphicTextInstance)", "ported": ["EterLib/GrpText.cpp, GrpFontTexture.cpp, GrpDIB.cpp (40250 verbatim)", "EterLib/GrpTextInstance.cpp (verbatim except D3D draw -> UI render commands)", "EterLib/TextTag, EterLocale/StringCodec, Arabic, StringCodec_Vietnamese (port_copy)", "EterLib/Util.cpp code page / font face table, SetDefaultCodePage", "UserInterface/GameType.cpp DefaultFont_* block", "CGraphicImageTexture Create/Lock/Unlock memory texture path", "GetMaxTextureWidth/Height"], "adapted": ["GDI subset (CreateFontIndirect, CreateDIBSection, TextOutW, GetTextExtentPoint32W, GetCharABCWidthsFloatW) emulated over vendored FreeType 2.13.3 with GDI cell-height/win-metric/gasp rules", "MultiByteToWideChar/WideCharToMultiByte: hand UTF-8/1252, iconv for other code pages", "glyph pages reach Godot as mem:<id>@<revision> memory textures; Canvas caches by id", "PythonBoot::Start calls SetDefaultCodePage(LocaleService_GetCodePage()) like 40250 Main", "CPythonResource registers fnt -> CGraphicText"], "divergent": ["Android /system/fonts has no Tahoma/Gulim: substitute faces change metrics until fonts are bundled", "no per-pixel comparison against Windows GDI output yet"], "needs_live": [], "tests": ["port.text: pass (Tahoma 12 height 12, width, PixelPositionToCharacterPosition, centre align, CP949 round trip)", "port.app_loop: pass (popup glyph quads, inked glyph page)", "ctest 31/31 pass", "godot --headless --path project --script python_host_test.gd: pass", "login screen screenshot: server list, popup and button captions rendered"], "todo": ["ExpandedImage blend modes", "Android font bundling", "2V1 login/select"]}
|
||||
{"time": "2026-09-23T05:25:43Z", "event": "port_round", "unit": "PORT-PLAN 2V1-a: EterLib network layer + EterBase cipher", "ported": ["EterLib/NetStream.cpp, NetAddress.cpp, NetDevice.cpp, NetPacketHeaderMap.cpp (port_copy verbatim)", "EterBase/cipher.cpp (port_copy verbatim, Crypto++ DH2 key agreement + CTR block ciphers)"], "adapted": ["shim/win32/winsock.h: Winsock names over BSD sockets; select ignores nfds and drops writable sockets without a peer (Winsock reports a failed connect only in exceptfds); EINPROGRESS -> WSAEWOULDBLOCK; ioctlsocket FIONBIO; WSAStartup ignores SIGPIPE"], "divergent": [], "needs_live": ["cipher agreement against the 40250 game server (2V1-e)"], "tests": ["port.net: pass (loopback connect, send/recv, peer close, refused-connect timeout, client/server cipher agreement)"], "todo": ["2V1-b PythonNetworkStream phases", "2V1-c net module", "2V1-d FakeServer", "2V1-e live smoke"]}
|
||||
|
||||
@@ -405,6 +405,14 @@ extension/third_party/cpython-2.7.18/ # 静态库(2P 批次
|
||||
2. **2V1 — 登录/选角**:加入 `PythonNetworkStream{,PhaseHandShake,PhaseLogin,PhaseSelect,PhaseLoading}.cpp`、
|
||||
`PythonNetworkStreamModule.cpp`、AccountConnector 及登录/选角脚本真实调用到的模块。验收为 FakeServer 离线完成 phase 顺序,
|
||||
再以真服 smoke 证明登录、角色列表和选择进入 Loading;未验证的服务器分支记 `NEEDS_LIVE`。
|
||||
|
||||
| 子步骤 | 内容 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| 2V1-a | `EterLib/NetStream`、`NetAddress`、`NetDevice`、`NetPacketHeaderMap`、`EterBase/cipher.cpp` | 完成:五个文件 `port_copy` 原样(含 32768 字节序列表、`_IMPROVED_PACKET_ENCRYPTION_` 的 Crypto++ DH2 + CTR 路径);`shim/win32/winsock.h` 把 Winsock 名字映射到 BSD socket,并补齐两处语义差异:`select` 忽略 nfds,connect 失败的 socket 不算可写(Winsock 只在 exceptfds 报,40250 靠超时判失败);非阻塞 connect 的 `EINPROGRESS` 报成 `WSAEWOULDBLOCK`;`WSAStartup` 忽略 SIGPIPE。`port.net` 用 loopback 覆盖连接、收发、对端断开、拒绝连接超时和双方密钥协商后的加解密 |
|
||||
| 2V1-b | `PythonNetworkStream` + HandShake/Login/Select/Loading phase + `AccountConnector` | 待做 |
|
||||
| 2V1-c | `net` 模块(`PythonNetworkStreamModule.cpp`)替换 2V0-e 的桩;`app.Create`/`Process` 接网络 | 待做 |
|
||||
| 2V1-d | FakeServer 离线跑完 HandShake→Login→Select→Loading | 待做 |
|
||||
| 2V1-e | 真服 smoke(凭据只从环境变量读) | 待做 |
|
||||
3. **2V2 — GamePhase/角色显示**:加入 GamePhase 的最小分派闭包、`PythonCharacterManager`、`InstanceBase`、`ActorInstance`
|
||||
及角色模型/动画/资源 adapter。验收为进入游戏、创建主角并显示可辨认的静止角色;这个切片要求 2D 的 proto 和所需资源通过
|
||||
strict gate。
|
||||
|
||||
@@ -174,6 +174,10 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
|
||||
add_test(NAME port.text COMMAND $<TARGET_FILE:port_text_test>)
|
||||
set_tests_properties(port.text PROPERTIES SKIP_RETURN_CODE 77)
|
||||
|
||||
add_executable(port_net_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_net_test.cpp)
|
||||
target_link_libraries(port_net_test PRIVATE port_platform)
|
||||
add_test(NAME port.net COMMAND $<TARGET_FILE:port_net_test>)
|
||||
|
||||
if(TARGET mtpython)
|
||||
add_executable(port_python_launcher_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_python_launcher_test.cpp)
|
||||
target_link_libraries(port_python_launcher_test PRIVATE port_platform)
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
#include "StdAfx.h"
|
||||
|
||||
#include "cipher.h"
|
||||
|
||||
#ifdef _IMPROVED_PACKET_ENCRYPTION_
|
||||
|
||||
//#pragma warning(push)
|
||||
//#pragma warning(disable: 4100 4127 4189 4231 4512 4706)
|
||||
|
||||
#include <cryptopp/modes.h>
|
||||
#include <cryptopp/nbtheory.h>
|
||||
#include <cryptopp/osrng.h>
|
||||
|
||||
// Diffie-Hellman key agreement
|
||||
#include <cryptopp/dh.h>
|
||||
#include <cryptopp/dh2.h>
|
||||
|
||||
// AES winner and candidates
|
||||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/cast.h>
|
||||
#include <cryptopp/rc6.h>
|
||||
#include <cryptopp/mars.h>
|
||||
#include <cryptopp/serpent.h>
|
||||
#include <cryptopp/twofish.h>
|
||||
// Other block ciphers
|
||||
#include <cryptopp/blowfish.h>
|
||||
#include <cryptopp/camellia.h>
|
||||
#include <cryptopp/des.h>
|
||||
#include <cryptopp/idea.h>
|
||||
#include <cryptopp/rc5.h>
|
||||
#include <cryptopp/seed.h>
|
||||
#include <cryptopp/shacal2.h>
|
||||
#include <cryptopp/skipjack.h>
|
||||
#include <cryptopp/tea.h>
|
||||
|
||||
#ifdef __THEMIDA__
|
||||
#include <ThemidaSDK.h>
|
||||
#endif
|
||||
|
||||
#include "Debug.h"
|
||||
|
||||
|
||||
using namespace CryptoPP;
|
||||
|
||||
// Block cipher algorithm selector abstract base class.
|
||||
struct BlockCipherAlgorithm {
|
||||
enum {
|
||||
kDefault, // to give more chances to default algorithm
|
||||
// AES winner and candidates
|
||||
// kAES, // Rijndael
|
||||
kRC6,
|
||||
kMARS,
|
||||
kTwofish,
|
||||
kSerpent,
|
||||
kCAST256,
|
||||
// Other block ciphers
|
||||
kIDEA,
|
||||
k3DES, // DES-EDE2
|
||||
kCamellia,
|
||||
kSEED,
|
||||
kRC5,
|
||||
kBlowfish,
|
||||
kTEA,
|
||||
//kSKIPJACK,
|
||||
kSHACAL2,
|
||||
// End sentinel
|
||||
kMaxAlgorithms
|
||||
};
|
||||
|
||||
BlockCipherAlgorithm() {}
|
||||
virtual ~BlockCipherAlgorithm() {}
|
||||
|
||||
static BlockCipherAlgorithm* Pick(int hint);
|
||||
|
||||
virtual int GetBlockSize() const = 0;
|
||||
virtual int GetDefaultKeyLength() const = 0;
|
||||
virtual int GetIVLength() const = 0;
|
||||
|
||||
virtual SymmetricCipher* CreateEncoder(const CryptoPP::byte* key, size_t keylen,
|
||||
const CryptoPP::byte* iv) const = 0;
|
||||
virtual SymmetricCipher* CreateDecoder(const CryptoPP::byte* key, size_t keylen,
|
||||
const CryptoPP::byte* iv) const = 0;
|
||||
};
|
||||
|
||||
// Block cipher (with CTR mode) algorithm selector template class.
|
||||
template<class T>
|
||||
struct BlockCipherDetail : public BlockCipherAlgorithm {
|
||||
BlockCipherDetail() {}
|
||||
virtual ~BlockCipherDetail() {}
|
||||
|
||||
virtual int GetBlockSize() const { return T::BLOCKSIZE; }
|
||||
virtual int GetDefaultKeyLength() const { return T::DEFAULT_KEYLENGTH; }
|
||||
virtual int GetIVLength() const { return T::IV_LENGTH; }
|
||||
|
||||
virtual SymmetricCipher* CreateEncoder(const CryptoPP::byte* key, size_t keylen,
|
||||
const CryptoPP::byte* iv) const
|
||||
{
|
||||
return new typename CTR_Mode<T>::Encryption(key, keylen, iv);
|
||||
}
|
||||
virtual SymmetricCipher* CreateDecoder(const CryptoPP::byte* key, size_t keylen,
|
||||
const CryptoPP::byte* iv) const
|
||||
{
|
||||
return new typename CTR_Mode<T>::Decryption(key, keylen, iv);
|
||||
}
|
||||
};
|
||||
|
||||
// Key agreement scheme abstract class.
|
||||
class KeyAgreement {
|
||||
public:
|
||||
KeyAgreement() {}
|
||||
virtual ~KeyAgreement() {}
|
||||
|
||||
virtual size_t Prepare(void* buffer, size_t* length) = 0;
|
||||
virtual bool Agree(size_t agreed_length, const void* buffer, size_t length) = 0;
|
||||
|
||||
const SecByteBlock& shared() const { return shared_; }
|
||||
|
||||
protected:
|
||||
SecByteBlock shared_;
|
||||
};
|
||||
|
||||
// Crypto++ Unified Diffie-Hellman key agreement scheme implementation.
|
||||
class DH2KeyAgreement : public KeyAgreement {
|
||||
public:
|
||||
DH2KeyAgreement();
|
||||
virtual ~DH2KeyAgreement();
|
||||
|
||||
virtual size_t Prepare(void* buffer, size_t* length);
|
||||
virtual bool Agree(size_t agreed_length, const void* buffer, size_t length);
|
||||
|
||||
private:
|
||||
DH dh_;
|
||||
DH2 dh2_;
|
||||
SecByteBlock spriv_key_;
|
||||
SecByteBlock epriv_key_;
|
||||
};
|
||||
|
||||
Cipher::Cipher()
|
||||
: activated_(false), encoder_(NULL), decoder_(NULL), key_agreement_(NULL) {
|
||||
}
|
||||
|
||||
Cipher::~Cipher() {
|
||||
if (activated_) {
|
||||
CleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
void Cipher::CleanUp() {
|
||||
if (encoder_ != NULL) {
|
||||
delete encoder_;
|
||||
encoder_ = NULL;
|
||||
}
|
||||
if (decoder_ != NULL) {
|
||||
delete decoder_;
|
||||
decoder_ = NULL;
|
||||
}
|
||||
if (key_agreement_ != NULL) {
|
||||
delete key_agreement_;
|
||||
key_agreement_ = NULL;
|
||||
}
|
||||
activated_ = false;
|
||||
}
|
||||
|
||||
size_t Cipher::Prepare(void* buffer, size_t* length) {
|
||||
#ifdef __THEMIDA__
|
||||
VM_START
|
||||
#endif
|
||||
|
||||
assert(key_agreement_ == NULL);
|
||||
key_agreement_ = new DH2KeyAgreement();
|
||||
assert(key_agreement_ != NULL);
|
||||
size_t agreed_length = key_agreement_->Prepare(buffer, length);
|
||||
if (agreed_length == 0) {
|
||||
delete key_agreement_;
|
||||
key_agreement_ = NULL;
|
||||
}
|
||||
#ifdef __THEMIDA__
|
||||
VM_END
|
||||
#endif
|
||||
|
||||
return agreed_length;
|
||||
}
|
||||
|
||||
bool Cipher::Activate(bool polarity, size_t agreed_length,
|
||||
const void* buffer, size_t length) {
|
||||
#ifdef __THEMIDA__
|
||||
VM_START
|
||||
#endif
|
||||
|
||||
assert(activated_ == false);
|
||||
assert(key_agreement_ != NULL);
|
||||
bool result = false;
|
||||
if (key_agreement_->Agree(agreed_length, buffer, length)) {
|
||||
result = SetUp(polarity);
|
||||
}
|
||||
delete key_agreement_;
|
||||
key_agreement_ = NULL;
|
||||
#ifdef __THEMIDA__
|
||||
VM_END
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Cipher::SetUp(bool polarity) {
|
||||
#ifdef __THEMIDA__
|
||||
VM_START
|
||||
#endif
|
||||
|
||||
assert(key_agreement_ != NULL);
|
||||
const SecByteBlock& shared = key_agreement_->shared();
|
||||
|
||||
// Pick a block cipher algorithm
|
||||
|
||||
if (shared.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int hint_0 = shared.BytePtr()[*(shared.BytePtr()) % shared.size()];
|
||||
int hint_1 = shared.BytePtr()[*(shared.BytePtr() + 1) % shared.size()];
|
||||
BlockCipherAlgorithm* detail_0 = BlockCipherAlgorithm::Pick(hint_0);
|
||||
BlockCipherAlgorithm* detail_1 = BlockCipherAlgorithm::Pick(hint_1);
|
||||
assert(detail_0 != NULL);
|
||||
assert(detail_1 != NULL);
|
||||
std::unique_ptr<BlockCipherAlgorithm> algorithm_0(detail_0);
|
||||
std::unique_ptr<BlockCipherAlgorithm> algorithm_1(detail_1);
|
||||
|
||||
const size_t key_length_0 = algorithm_0->GetDefaultKeyLength();
|
||||
const size_t iv_length_0 = algorithm_0->GetBlockSize();
|
||||
|
||||
if (shared.size() < key_length_0 || shared.size() < iv_length_0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t key_length_1 = algorithm_1->GetDefaultKeyLength();
|
||||
const size_t iv_length_1 = algorithm_1->GetBlockSize();
|
||||
|
||||
if (shared.size() < key_length_1 || shared.size() < iv_length_1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pick encryption keys and initial vectors
|
||||
|
||||
SecByteBlock key_0(key_length_0), iv_0(iv_length_0);
|
||||
SecByteBlock key_1(key_length_1), iv_1(iv_length_1);
|
||||
|
||||
size_t offset;
|
||||
|
||||
key_0.Assign(shared, key_length_0);
|
||||
offset = key_length_0;
|
||||
offset = min(key_length_0, shared.size() - key_length_1);
|
||||
key_1.Assign(shared.BytePtr() + offset, key_length_1);
|
||||
|
||||
offset = shared.size() - iv_length_0;
|
||||
iv_0.Assign(shared.BytePtr() + offset, iv_length_0);
|
||||
offset = (offset < iv_length_1 ? 0 : offset - iv_length_1);
|
||||
iv_1.Assign(shared.BytePtr() + offset, iv_length_1);
|
||||
|
||||
// Create encryption/decryption objects
|
||||
|
||||
if (polarity) {
|
||||
encoder_ = algorithm_1->CreateEncoder(key_1, key_1.size(), iv_1);
|
||||
decoder_ = algorithm_0->CreateDecoder(key_0, key_0.size(), iv_0);
|
||||
} else {
|
||||
encoder_ = algorithm_0->CreateEncoder(key_0, key_0.size(), iv_0);
|
||||
decoder_ = algorithm_1->CreateDecoder(key_1, key_1.size(), iv_1);
|
||||
}
|
||||
|
||||
assert(encoder_ != NULL);
|
||||
assert(decoder_ != NULL);
|
||||
#ifdef __THEMIDA__
|
||||
VM_END
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BlockCipherAlgorithm* BlockCipherAlgorithm::Pick(int hint) {
|
||||
BlockCipherAlgorithm* detail;
|
||||
int selector = hint % kMaxAlgorithms;
|
||||
switch (selector) {
|
||||
//case kAES:
|
||||
// detail = new BlockCipherDetail<AES>();
|
||||
break;
|
||||
case kRC6:
|
||||
detail = new BlockCipherDetail<RC6>();
|
||||
break;
|
||||
case kMARS:
|
||||
detail = new BlockCipherDetail<MARS>();
|
||||
break;
|
||||
case kTwofish:
|
||||
detail = new BlockCipherDetail<Twofish>();
|
||||
break;
|
||||
case kSerpent:
|
||||
detail = new BlockCipherDetail<Serpent>();
|
||||
break;
|
||||
case kCAST256:
|
||||
detail = new BlockCipherDetail<CAST256>();
|
||||
break;
|
||||
case kIDEA:
|
||||
detail = new BlockCipherDetail<IDEA>();
|
||||
break;
|
||||
case k3DES:
|
||||
detail = new BlockCipherDetail<DES_EDE2>();
|
||||
break;
|
||||
case kCamellia:
|
||||
detail = new BlockCipherDetail<Camellia>();
|
||||
break;
|
||||
case kSEED:
|
||||
detail = new BlockCipherDetail<SEED>();
|
||||
break;
|
||||
case kRC5:
|
||||
detail = new BlockCipherDetail<RC5>();
|
||||
break;
|
||||
case kBlowfish:
|
||||
detail = new BlockCipherDetail<Blowfish>();
|
||||
break;
|
||||
case kTEA:
|
||||
detail = new BlockCipherDetail<TEA>();
|
||||
break;
|
||||
// case kSKIPJACK:
|
||||
// detail = new BlockCipherDetail<SKIPJACK>();
|
||||
// break;
|
||||
case kSHACAL2:
|
||||
detail = new BlockCipherDetail<SHACAL2>();
|
||||
break;
|
||||
case kDefault:
|
||||
default:
|
||||
detail = new BlockCipherDetail<Twofish>(); // default algorithm
|
||||
break;
|
||||
}
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
DH2KeyAgreement::DH2KeyAgreement() : dh_(), dh2_(dh_) {
|
||||
}
|
||||
|
||||
DH2KeyAgreement::~DH2KeyAgreement() {
|
||||
}
|
||||
|
||||
size_t DH2KeyAgreement::Prepare(void* buffer, size_t* length) {
|
||||
#ifdef __THEMIDA__
|
||||
VM_START
|
||||
#endif
|
||||
|
||||
// RFC 5114, 1024-bit MODP Group with 160-bit Prime Order Subgroup
|
||||
// http://tools.ietf.org/html/rfc5114#section-2.1
|
||||
Integer p("0xB10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C6"
|
||||
"9A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C0"
|
||||
"13ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD70"
|
||||
"98488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0"
|
||||
"A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708"
|
||||
"DF1FB2BC2E4A4371");
|
||||
|
||||
Integer g("0xA4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507F"
|
||||
"D6406CFF14266D31266FEA1E5C41564B777E690F5504F213"
|
||||
"160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1"
|
||||
"909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28A"
|
||||
"D662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24"
|
||||
"855E6EEB22B3B2E5");
|
||||
|
||||
Integer q("0xF518AA8781A8DF278ABA4E7D64B7CB9D49462353");
|
||||
|
||||
// Schnorr Group primes are of the form p = rq + 1, p and q prime. They
|
||||
// provide a subgroup order. In the case of 1024-bit MODP Group, the
|
||||
// security level is 80 bits (based on the 160-bit prime order subgroup).
|
||||
|
||||
// For a compare/contrast of using the maximum security level, see
|
||||
// dh-unified.zip. Also see http://www.cryptopp.com/wiki/Diffie-Hellman
|
||||
// and http://www.cryptopp.com/wiki/Security_level .
|
||||
|
||||
AutoSeededRandomPool rnd;
|
||||
|
||||
dh_.AccessGroupParameters().Initialize(p, q, g);
|
||||
|
||||
if(!dh_.GetGroupParameters().ValidateGroup(rnd, 3)) {
|
||||
// Failed to validate prime and generator
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t count = 0;
|
||||
|
||||
p = dh_.GetGroupParameters().GetModulus();
|
||||
q = dh_.GetGroupParameters().GetSubgroupOrder();
|
||||
g = dh_.GetGroupParameters().GetGenerator();
|
||||
|
||||
// http://groups.google.com/group/sci.crypt/browse_thread/thread/7dc7eeb04a09f0ce
|
||||
Integer v = ModularExponentiation(g, q, p);
|
||||
|
||||
if(v != Integer::One()) {
|
||||
// Failed to verify order of the subgroup
|
||||
return 0;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
spriv_key_.New(dh2_.StaticPrivateKeyLength());
|
||||
epriv_key_.New(dh2_.EphemeralPrivateKeyLength());
|
||||
SecByteBlock spub_key(dh2_.StaticPublicKeyLength());
|
||||
SecByteBlock epub_key(dh2_.EphemeralPublicKeyLength());
|
||||
|
||||
dh2_.GenerateStaticKeyPair(rnd, spriv_key_, spub_key);
|
||||
dh2_.GenerateEphemeralKeyPair(rnd, epriv_key_, epub_key);
|
||||
|
||||
// Prepare key agreement data
|
||||
const size_t spub_key_length = spub_key.size();
|
||||
const size_t epub_key_length = epub_key.size();
|
||||
const size_t data_length = spub_key_length + epub_key_length;
|
||||
|
||||
if (*length < data_length) {
|
||||
// Not enough data buffer length b-l-a-c-k
|
||||
return 0;
|
||||
}
|
||||
|
||||
*length = data_length;
|
||||
CryptoPP::byte* buf = (CryptoPP::byte*)buffer;
|
||||
memcpy(buf, spub_key.BytePtr(), spub_key_length);
|
||||
memcpy(buf + spub_key_length, epub_key.BytePtr(), epub_key_length);
|
||||
|
||||
#ifdef __THEMIDA__
|
||||
VM_END
|
||||
#endif
|
||||
|
||||
return dh2_.AgreedValueLength();
|
||||
}
|
||||
|
||||
bool DH2KeyAgreement::Agree(size_t agreed_length, const void* buffer, size_t length) {
|
||||
if (agreed_length != dh2_.AgreedValueLength()) {
|
||||
// Shared secret size mismatch
|
||||
return false;
|
||||
}
|
||||
const size_t spub_key_length = dh2_.StaticPublicKeyLength();
|
||||
const size_t epub_key_length = dh2_.EphemeralPublicKeyLength();
|
||||
if (length != (spub_key_length + epub_key_length)) {
|
||||
// Wrong data length
|
||||
return false;
|
||||
}
|
||||
shared_.New(dh2_.AgreedValueLength());
|
||||
const CryptoPP::byte* buf = (const CryptoPP::byte*)buffer;
|
||||
if (!dh2_.Agree(shared_, spriv_key_, epriv_key_, buf, buf + spub_key_length)) {
|
||||
// Failed to reach shared secret
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,107 @@
|
||||
#include "StdAfx.h"
|
||||
#include "NetAddress.h"
|
||||
|
||||
#ifndef VC_EXTRALEAN
|
||||
|
||||
bool CNetworkAddress::GetHostName(char* szName, int size)
|
||||
{
|
||||
if (gethostname(szName, size)==SOCKET_ERROR)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
CNetworkAddress::CNetworkAddress()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
CNetworkAddress::~CNetworkAddress()
|
||||
{
|
||||
}
|
||||
|
||||
CNetworkAddress::operator const SOCKADDR_IN&() const
|
||||
{
|
||||
return m_sockAddrIn;
|
||||
}
|
||||
|
||||
void CNetworkAddress::Clear()
|
||||
{
|
||||
memset(&m_sockAddrIn, 0, sizeof(m_sockAddrIn));
|
||||
m_sockAddrIn.sin_family=AF_INET;
|
||||
}
|
||||
|
||||
bool CNetworkAddress::IsIP(const char* c_szAddr)
|
||||
{
|
||||
if (c_szAddr[0]<'0' || c_szAddr[0]>'9')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetworkAddress::Set(const char* c_szAddr, int port)
|
||||
{
|
||||
if (IsIP(c_szAddr))
|
||||
{
|
||||
SetIP(c_szAddr);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!SetDNS(c_szAddr))
|
||||
return false;
|
||||
}
|
||||
|
||||
SetPort(port);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetworkAddress::SetLocalIP()
|
||||
{
|
||||
SetIP(INADDR_ANY);
|
||||
}
|
||||
|
||||
void CNetworkAddress::SetIP(DWORD ip)
|
||||
{
|
||||
m_sockAddrIn.sin_addr.s_addr=htonl(ip);
|
||||
}
|
||||
|
||||
void CNetworkAddress::SetIP(const char* c_szIP)
|
||||
{
|
||||
m_sockAddrIn.sin_addr.s_addr=inet_addr(c_szIP);
|
||||
}
|
||||
|
||||
bool CNetworkAddress::SetDNS(const char* c_szDNS)
|
||||
{
|
||||
HOSTENT* pHostent=gethostbyname(c_szDNS);
|
||||
if (!pHostent) return false;
|
||||
memcpy(&m_sockAddrIn.sin_addr, pHostent->h_addr, sizeof(m_sockAddrIn.sin_addr));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetworkAddress::SetPort(int port)
|
||||
{
|
||||
m_sockAddrIn.sin_port = htons(port);
|
||||
}
|
||||
|
||||
int CNetworkAddress::GetSize()
|
||||
{
|
||||
return sizeof(m_sockAddrIn);
|
||||
}
|
||||
|
||||
DWORD CNetworkAddress::GetIP()
|
||||
{
|
||||
return ntohl(m_sockAddrIn.sin_addr.s_addr);
|
||||
}
|
||||
|
||||
void CNetworkAddress::GetIP(char* szIP, int len)
|
||||
{
|
||||
BYTE IPs[4];
|
||||
*((DWORD*)IPs)=m_sockAddrIn.sin_addr.s_addr;
|
||||
|
||||
_snprintf(szIP, len, "%d.%d.%d.%d", IPs[0], IPs[1], IPs[2], IPs[3]);
|
||||
}
|
||||
|
||||
int CNetworkAddress::GetPort()
|
||||
{
|
||||
return ntohs(m_sockAddrIn.sin_port);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "StdAfx.h"
|
||||
#include "NetDevice.h"
|
||||
|
||||
CNetworkDevice::CNetworkDevice()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CNetworkDevice::~CNetworkDevice()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void CNetworkDevice::Initialize()
|
||||
{
|
||||
m_isWSA=false;
|
||||
}
|
||||
|
||||
void CNetworkDevice::Destroy()
|
||||
{
|
||||
if (m_isWSA)
|
||||
{
|
||||
WSACleanup();
|
||||
m_isWSA=false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CNetworkDevice::Create()
|
||||
{
|
||||
Destroy();
|
||||
|
||||
Initialize();
|
||||
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(1, 1), &wsaData)!=0)
|
||||
return false;
|
||||
|
||||
m_isWSA=true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "StdAfx.h"
|
||||
#include "NetPacketHeaderMap.h"
|
||||
|
||||
void CNetworkPacketHeaderMap::Set(int header, TPacketType rPacketType)
|
||||
{
|
||||
m_headerMap[header] = rPacketType;
|
||||
}
|
||||
bool CNetworkPacketHeaderMap::Get(int header, TPacketType * pPacketType)
|
||||
{
|
||||
std::map<int, TPacketType>::iterator f=m_headerMap.find(header);
|
||||
|
||||
if (m_headerMap.end()==f)
|
||||
return false;
|
||||
|
||||
*pPacketType = f->second;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CNetworkPacketHeaderMap::CNetworkPacketHeaderMap()
|
||||
{
|
||||
}
|
||||
|
||||
CNetworkPacketHeaderMap::~CNetworkPacketHeaderMap()
|
||||
{
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,75 @@
|
||||
#pragma once
|
||||
// Shim for <winsock.h> on non-Windows targets: the BSD socket types under their Winsock names.
|
||||
// Shim for <winsock.h> on non-Windows targets: the BSD socket API under its Winsock names, with the
|
||||
// Winsock semantics 40250 relies on where the two differ (see the notes on each function).
|
||||
#include "windows.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef int SOCKET;
|
||||
typedef struct sockaddr SOCKADDR;
|
||||
typedef struct sockaddr* PSOCKADDR;
|
||||
typedef struct sockaddr_in SOCKADDR_IN;
|
||||
typedef struct in_addr IN_ADDR;
|
||||
typedef struct timeval TIMEVAL;
|
||||
typedef struct hostent HOSTENT;
|
||||
#define INVALID_SOCKET (-1)
|
||||
#define SOCKET_ERROR (-1)
|
||||
#define WSAEWOULDBLOCK EWOULDBLOCK
|
||||
|
||||
typedef struct WSAData
|
||||
{
|
||||
WORD wVersion;
|
||||
WORD wHighVersion;
|
||||
} WSADATA;
|
||||
|
||||
// There is no Winsock DLL to start. A send on a socket the peer has closed raises SIGPIPE on POSIX
|
||||
// where Winsock returns an error, so the process ignores it from here on.
|
||||
inline int WSAStartup(WORD version, WSADATA* data)
|
||||
{
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
if (data)
|
||||
data->wVersion = data->wHighVersion = version;
|
||||
return 0;
|
||||
}
|
||||
inline int WSACleanup() { return 0; }
|
||||
|
||||
// A non-blocking connect() answers WSAEWOULDBLOCK on Winsock and EINPROGRESS on POSIX.
|
||||
inline int WSAGetLastError() { return errno == EINPROGRESS ? WSAEWOULDBLOCK : errno; }
|
||||
|
||||
inline int closesocket(SOCKET s) { return close(s); }
|
||||
|
||||
inline int ioctlsocket(SOCKET s, long cmd, DWORD* argp)
|
||||
{
|
||||
int value = static_cast<int>(*argp);
|
||||
return ioctl(s, static_cast<unsigned long>(cmd), &value);
|
||||
}
|
||||
|
||||
// Winsock ignores nfds, and a socket whose connect failed is reported only in exceptfds; POSIX needs
|
||||
// nfds and marks such a socket writable. Callers pass 0 and treat "writable" as "connected"
|
||||
// (CNetworkStream::Process), so writable sockets without a peer are dropped from the write set.
|
||||
inline int mt_winsock_select(fd_set* readfds, fd_set* writefds, fd_set* exceptfds, TIMEVAL* timeout)
|
||||
{
|
||||
int ret = ::select(FD_SETSIZE, readfds, writefds, exceptfds, timeout);
|
||||
if (ret <= 0 || !writefds)
|
||||
return ret;
|
||||
for (int fd = 0; fd < FD_SETSIZE; ++fd)
|
||||
{
|
||||
if (!FD_ISSET(fd, writefds))
|
||||
continue;
|
||||
sockaddr_in peer;
|
||||
socklen_t len = sizeof(peer);
|
||||
if (getpeername(fd, reinterpret_cast<sockaddr*>(&peer), &len) != 0)
|
||||
FD_CLR(fd, writefds);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#define select(nfds, readfds, writefds, exceptfds, timeout) mt_winsock_select(readfds, writefds, exceptfds, timeout)
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// 批次 2V1-a: the 40250 EterLib network layer (CNetworkStream over the winsock shim) against a
|
||||
// loopback peer: non-blocking connect, buffered send/recv, the connect-failure timeout, and the
|
||||
// Crypto++ key agreement (_IMPROVED_PACKET_ENCRYPTION_) with a second Cipher playing the server.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/NetDevice.h"
|
||||
#include "EterLib/NetStream.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
static int g_failures = 0;
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::fprintf(stderr, "%s:%d: CHECK(%s)\n", __FILE__, __LINE__, #cond); \
|
||||
++g_failures; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
class TestStream : public CNetworkStream
|
||||
{
|
||||
public:
|
||||
int successes = 0, failures = 0, remote_disconnects = 0;
|
||||
|
||||
using CNetworkStream::Prepare;
|
||||
using CNetworkStream::Activate;
|
||||
using CNetworkStream::ActivateCipher;
|
||||
|
||||
protected:
|
||||
void OnConnectSuccess() override { ++successes; }
|
||||
void OnConnectFailure() override { ++failures; }
|
||||
void OnRemoteDisconnect() override { ++remote_disconnects; }
|
||||
};
|
||||
|
||||
static int listen_loopback(int* port)
|
||||
{
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
sockaddr_in addr = {};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
|
||||
socklen_t len = sizeof(addr);
|
||||
getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &len);
|
||||
listen(fd, 1);
|
||||
*port = ntohs(addr.sin_port);
|
||||
return fd;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
static bool pump(TestStream& stream, F done, int ms = 2000)
|
||||
{
|
||||
for (int i = 0; i < ms; ++i)
|
||||
{
|
||||
stream.Process();
|
||||
if (done())
|
||||
return true;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool read_exact(int fd, void* buf, size_t len)
|
||||
{
|
||||
char* p = static_cast<char*>(buf);
|
||||
while (len)
|
||||
{
|
||||
ssize_t n = recv(fd, p, len, 0);
|
||||
if (n <= 0)
|
||||
return false;
|
||||
p += n;
|
||||
len -= static_cast<size_t>(n);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void connect_and_exchange()
|
||||
{
|
||||
int port = 0;
|
||||
int listener = listen_loopback(&port);
|
||||
|
||||
TestStream stream;
|
||||
stream.SetRecvBufferSize(4096);
|
||||
stream.SetSendBufferSize(4096);
|
||||
CHECK(stream.Connect("127.0.0.1", port));
|
||||
CHECK(pump(stream, [&] { return stream.IsOnline(); }));
|
||||
CHECK(stream.successes == 1 && stream.failures == 0);
|
||||
int peer = accept(listener, nullptr, nullptr);
|
||||
CHECK(peer >= 0);
|
||||
|
||||
// Plain bytes both ways; Send only queues, Process flushes.
|
||||
CHECK(stream.Send(5, "hello"));
|
||||
pump(stream, [] { return true; }, 1);
|
||||
char got[16] = {};
|
||||
CHECK(read_exact(peer, got, 5) && std::memcmp(got, "hello", 5) == 0);
|
||||
send(peer, "world!", 6, 0);
|
||||
CHECK(pump(stream, [&] { return stream.GetRecvBufferSize() >= 6; }));
|
||||
char peek[6] = {};
|
||||
CHECK(stream.Peek(6, peek) && std::memcmp(peek, "world!", 6) == 0);
|
||||
CHECK(stream.Recv(6, got) && stream.GetRecvBufferSize() == 0);
|
||||
CHECK(!stream.Recv(1, got));
|
||||
|
||||
// Key agreement: each side prepares, swaps its public value and activates with the opposite
|
||||
// polarity (the 40250 game server calls Activate(false, ...)).
|
||||
Cipher server;
|
||||
std::vector<unsigned char> client_pub(1024), server_pub(1024);
|
||||
size_t client_len = client_pub.size(), server_len = server_pub.size();
|
||||
size_t client_agreed = stream.Prepare(client_pub.data(), &client_len);
|
||||
size_t server_agreed = server.Prepare(server_pub.data(), &server_len);
|
||||
CHECK(client_agreed > 0 && client_agreed == server_agreed);
|
||||
CHECK(stream.Activate(client_agreed, server_pub.data(), server_len));
|
||||
CHECK(server.Activate(false, server_agreed, client_pub.data(), client_len));
|
||||
stream.ActivateCipher();
|
||||
server.set_activated(true);
|
||||
CHECK(stream.IsSecurityMode());
|
||||
|
||||
// Client → server: ciphertext on the wire, the server's decoder restores it.
|
||||
const char secret[] = "secret-login-packet";
|
||||
CHECK(stream.Send(sizeof(secret), secret));
|
||||
pump(stream, [] { return true; }, 1);
|
||||
char wire[sizeof(secret)] = {};
|
||||
CHECK(read_exact(peer, wire, sizeof(wire)));
|
||||
CHECK(std::memcmp(wire, secret, sizeof(secret)) != 0);
|
||||
server.Decrypt(wire, sizeof(wire));
|
||||
CHECK(std::memcmp(wire, secret, sizeof(secret)) == 0);
|
||||
|
||||
// Server → client.
|
||||
char reply[] = "phase-select";
|
||||
server.Encrypt(reply, sizeof(reply));
|
||||
send(peer, reply, sizeof(reply), 0);
|
||||
CHECK(pump(stream, [&] { return stream.GetRecvBufferSize() >= static_cast<int>(sizeof(reply)); }));
|
||||
CHECK(stream.Recv(sizeof(reply), got) && std::strcmp(got, "phase-select") == 0);
|
||||
|
||||
// The peer hangs up: recv answers 0, Process reports it once and clears the stream.
|
||||
close(peer);
|
||||
CHECK(pump(stream, [&] { return stream.remote_disconnects > 0; }));
|
||||
CHECK(stream.remote_disconnects == 1 && !stream.IsOnline() && !stream.IsSecurityMode());
|
||||
close(listener);
|
||||
}
|
||||
|
||||
static void connect_refused()
|
||||
{
|
||||
// A port nobody listens on: Winsock reports the failed connect only in exceptfds, which
|
||||
// CNetworkStream::Process does not pass, so the stream waits out the limit and then fails.
|
||||
int port = 0;
|
||||
int listener = listen_loopback(&port);
|
||||
close(listener);
|
||||
|
||||
TestStream stream;
|
||||
CNetworkAddress addr;
|
||||
addr.Set("127.0.0.1", port);
|
||||
CHECK(stream.Connect(addr, 1));
|
||||
CHECK(pump(stream, [&] { return stream.failures > 0; }, 3000));
|
||||
CHECK(stream.successes == 0 && stream.failures == 1 && !stream.IsOnline());
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
CNetworkDevice device;
|
||||
CHECK(device.Create());
|
||||
connect_and_exchange();
|
||||
connect_refused();
|
||||
|
||||
if (g_failures)
|
||||
{
|
||||
std::fprintf(stderr, "%d check(s) failed\n", g_failures);
|
||||
return 1;
|
||||
}
|
||||
std::printf("port_net_test: ok\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user