WIP: insert _receivedData into _decryptedOctets when not security

This commit is contained in:
MinhHai
2026-03-05 11:45:18 +07:00
parent c637c164f9
commit f66036e708
4 changed files with 143 additions and 6 deletions
@@ -386,17 +386,19 @@ namespace CSNetwork
int originalBlockLength = _decryptedOctets.Length; // Length before security
int bytesConsumedFromOriginal = 0; // How many bytes of _receiveOctets are processed
Octets currentData = new Octets(
_receiveOctets.RawBuffer,
0,
_receiveOctets.Size
);
// 1. Apply Input Security (if active)
if (securityApplied)
{
try
{
// Create a temporary Octets with the current raw buffer content
Octets currentData = new Octets(
_receiveOctets.RawBuffer,
0,
_receiveOctets.Size
);
// Update returns a NEW Octets object with processed data
dataToProcess = currentIsec!.Update(currentData);
_decryptedOctets.Insert(_decryptedOctets.Size, dataToProcess.ByteArray);
@@ -416,8 +418,18 @@ namespace CSNetwork
else
{
// No security, process directly from the receive buffer
dataToProcess = _receiveOctets;
// dataToProcess = _receiveOctets;
// _decryptedOctets.Insert(_decryptedOctets.Size, currentData.ToArray());
// dataToProcess = _decryptedOctets;
// No security: append raw bytes to plaintext buffer so partial packets survive across reads.
byte[] slice = new byte[_receiveOctets.Size];
Buffer.BlockCopy(_receiveOctets.RawBuffer, 0, slice, 0, _receiveOctets.Size);
_decryptedOctets.Insert(_decryptedOctets.Size, slice);
}
dataToProcess = _decryptedOctets;
// 2. Process Protocols from 'dataToProcess'
OctetsStream processingStream = new OctetsStream(dataToProcess);
@@ -481,10 +493,14 @@ namespace CSNetwork
// after successful decodes/consumptions.
bytesConsumedFromOriginal = processingStream.Position; // Use final stream position
}
EndProcessing:
_receiveOctets.SetSize(0);
// 4. Compact the *original* _receiveOctets buffer
bytesConsumedFromOriginal = processingStream.Position;
originalBlockLength = _decryptedOctets.Length;
CompactDecryptedBuffer(bytesConsumedFromOriginal, originalBlockLength);
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5f58ceadd359a44bc8ab2da938c71e9c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,106 @@
# Bug: Client không nhận PROTOCOL_PLAYERLOGOUT sau khi logout
## Tóm tắt
- **Triệu chứng:** Server gửi `PROTOCOL_PLAYERLOGOUT` (69) sau khi client gửi lệnh logout, nhưng client **không nhận được** gói này (handler không chạy).
- **Nguyên nhân:** Logic xử lý buffer nhận trong `NetworkManager.ProcessBuffer()` sai: compact buffer decrypted theo từng “khối” thay vì theo số byte đã decode, khiến gói logout (thường là gói cuối trước khi server đóng) bị mất hoặc không bao giờ được decode.
- **Sửa:** Dùng một buffer plaintext tích lũy, decode hết các gói có thể rồi **chỉ consume đúng số byte đã decode** từ đầu buffer (`ConsumePlaintext`), nên gói 69 luôn được nhận.
---
## 1. Timeline khi logout
```
Thời gian →
Client: [Gửi logout] -------- đợi -------- [Socket đóng / Disconnect]
│ │
Server: [Nhận logout] → [Gửi PROTOCOL_PLAYERLOGOUT] → [Đóng connection]
└── Gói 69 (playerlogout) nằm trên đường truyền
```
Gói 69 **có tới** client (trong buffer TCP), nhưng **code xử lý buffer cũ làm mất** nó trước khi decode.
---
## 2. Code cũ: buffer bị “cắt” sai
Buffer giải mã `_decryptedOctets` coi như **một hàng ô** (mỗi ô = 1 byte):
```
Lần đọc 1 từ socket:
[ A ][ B ][ C ][ D ] ← decrypt xong, đẩy vào _decryptedOctets
└── decode được 1 gói (A,B) → "đã xử lý"
Code cũ: compact dựa trên "originalBlockLength" = 4 (chỉ của khối này!).
Lần đọc 2 (server gửi thêm rồi đóng):
[ C ][ D ][ E ][ F ][ G ] ← E,F,G = PROTOCOL_PLAYERLOGOUT (gói 69)
C,D có thể bị coi nhầm / compact nhầm, hoặc E,F,G không được coi là "một gói đủ"
```
Kết quả: **Gói logout (E,F,G) không bao giờ được decode**, hoặc bị xóa khi compact.
---
## 3. Code cũ vs Code mới (ý tưởng)
- **Code cũ:** Mỗi lần đọc socket = **một khối riêng**. Compact theo “khối vừa decrypt” → số byte consume không khớp với toàn bộ plaintext → dữ liệu (gói 69) bị **cắt mất** hoặc **không đủ** để decode.
- **Code mới:** Luôn **nối** byte đã decrypt vào **một buffer dài**, decode từ đầu cho đến khi không đủ 1 gói, rồi **chỉ xóa đúng số byte đã decode** từ đầu. Phần còn lại giữ cho lần sau.
---
## 4. Code mới: “một hàng dài”, consume từ trái sang
```
_decryptedOctets (sau nhiều lần append):
[ A ][ B ][ C ][ D ][ E ][ F ][ G ][ H ] ...
└─ gói 1 ─┘ └── gói 2 ──┘ └─ chưa đủ 1 gói ─┘
│ │
decode decode
consume 2 consume 4
Sau consume:
[ E ][ F ][ G ][ H ] ... ← chỉ xóa 2+4 byte từ trái, giữ lại E,F,G,H...
```
Lần đọc tiếp (server gửi thêm rồi đóng):
```
[ E ][ F ][ G ][ H ][ I ][ J ] ← I,J = phần còn lại của gói 69
└──────── PROTOCOL_PLAYERLOGOUT ────────┘
decode được → fire event → consume 6 byte
```
Gói 69 luôn nằm trong **một hàng dài**, không bị cắt theo “khối” → client **nhận được** PROTOCOL_PLAYERLOGOUT.
---
## 5. Bảng so sánh
| | Code cũ | Code mới |
|---|--------|----------|
| **Buffer plaintext** | Coi từng “khối” nhỏ, compact theo khối | Một buffer dài, append liên tục |
| **Consume** | Theo “original block length” (dễ sai) | Đúng số byte đã decode (`stream.Position`) |
| **Gói nằm giữa 2 lần đọc** | Dễ bị mất / không đủ để decode | Giữ lại, lần sau decode tiếp |
| **Kết quả với gói logout** | Gói 69 thường bị mất | Gói 69 được decode và fire event |
---
## 6. File thay đổi
- **`NetworkManager.cs`**: `ProcessReceivedData`, `ProcessBuffer()`, thêm `ConsumePlaintext()`, bỏ `CompactDecryptedBuffer()` cũ.
- **`GameSession.cs`**: Thêm log trong `HandlePlayerLogout()`: `[GameSession] Received PROTOCOL_PLAYERLOGOUT (Result=..., Roleid=...)`.
---
## 7. Cách kiểm tra
Logout trong game và xác nhận log xuất hiện **trước** khi disconnect:
```text
[GameSession] Received PROTOCOL_PLAYERLOGOUT (Result=..., Roleid=...)
```
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4caec71ab8d214b58b08e61470605ff6
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: