net: PACKET_FLAG_CHOKED = 1<<4 (раньше был 1<<3 = Split)
Взят прямо из Source.NET (IConnectionlessPacketHandler.cs, enum PacketFlag): Reliable=1, Compressed=2, Encrypted=4, Split=8, Choked=16, Challenge=32. Из-за неверного бита в пакетах сервера с выставленным битом 3 читался лишний байт, reliable_state и challenge уезжали на 8 бит. Добавлены тесты на константы флагов и на заголовок с Choked.
This commit is contained in:
parent
c6b9b04404
commit
459fe6b0bc
2 changed files with 89 additions and 3 deletions
|
|
@ -46,12 +46,12 @@ pub fn subchannel_bits() -> usize {
|
|||
/// Число потоков фрагментов (Normal, File).
|
||||
pub const MAX_STREAMS: usize = 2;
|
||||
|
||||
// Флаги пакета.
|
||||
// Флаги пакета (`PacketFlag` в Source.Common/Networking/IConnectionlessPacketHandler.cs).
|
||||
pub const PACKET_FLAG_RELIABLE: u8 = 1 << 0;
|
||||
pub const PACKET_FLAG_COMPRESSED: u8 = 1 << 1;
|
||||
pub const PACKET_FLAG_ENCRYPTED: u8 = 1 << 2;
|
||||
pub const PACKET_FLAG_CHOKED: u8 = 1 << 3;
|
||||
/// GMod-дополнение: в пакете присутствует challenge (0x20, не 0x10).
|
||||
pub const PACKET_FLAG_SPLIT: u8 = 1 << 3;
|
||||
pub const PACKET_FLAG_CHOKED: u8 = 1 << 4;
|
||||
pub const PACKET_FLAG_CHALLENGE: u8 = 1 << 5;
|
||||
|
||||
/// Net-сообщения (нумерация GMod).
|
||||
|
|
@ -765,6 +765,46 @@ mod tests {
|
|||
let _ = parse_messages(&packet, header.payload_bit);
|
||||
}
|
||||
|
||||
/// Флаги `PacketFlag` — по спецификации Source.NET:
|
||||
/// Reliable=1, Compressed=2, Encrypted=4, Split=8, Choked=16, Challenge=32.
|
||||
///
|
||||
/// Раньше `CHOKED` был объявлен как `1<<3`, то есть фактически `Split`.
|
||||
/// Из-за этого в пакетах сервера с выставленным битом 3 читался лишний
|
||||
/// байт «choked», и `reliable_state` с `challenge` уезжали на 8 бит.
|
||||
#[test]
|
||||
fn packet_flags_match_specification() {
|
||||
assert_eq!(PACKET_FLAG_RELIABLE, 0x01);
|
||||
assert_eq!(PACKET_FLAG_COMPRESSED, 0x02);
|
||||
assert_eq!(PACKET_FLAG_ENCRYPTED, 0x04);
|
||||
assert_eq!(PACKET_FLAG_SPLIT, 0x08);
|
||||
assert_eq!(PACKET_FLAG_CHOKED, 0x10);
|
||||
assert_eq!(PACKET_FLAG_CHALLENGE, 0x20);
|
||||
}
|
||||
|
||||
/// Заголовок с флагом Choked: между reliable_state и challenge
|
||||
/// вставляется байт «сколько пакетов пропущено».
|
||||
#[test]
|
||||
fn header_with_choked_flag_has_extra_byte() {
|
||||
let mut packet = Vec::new();
|
||||
packet.extend_from_slice(&7u32.to_le_bytes()); // sequence
|
||||
packet.extend_from_slice(&3u32.to_le_bytes()); // ack
|
||||
packet.push(PACKET_FLAG_CHOKED);
|
||||
packet.extend_from_slice(&0u16.to_le_bytes()); // checksum, патчится ниже
|
||||
packet.extend_from_slice(&0x1234u16.to_le_bytes()); // reliable state
|
||||
packet.push(4); // choked
|
||||
// Далее — payload; challenge не выставлен.
|
||||
let csum = fold_crc32(&packet[11..]);
|
||||
packet[9..11].copy_from_slice(&csum.to_le_bytes());
|
||||
|
||||
let header = parse_packet(&packet, true).expect("заголовок");
|
||||
assert_eq!(header.sequence, 7);
|
||||
assert_eq!(header.ack, 3);
|
||||
assert_eq!(header.flags & PACKET_FLAG_CHOKED, PACKET_FLAG_CHOKED);
|
||||
assert_eq!(header.reliable_state, 0x1234, "reliable_state не сдвинут");
|
||||
assert_eq!(header.choked, Some(4));
|
||||
assert!(header.checksum_ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checksum_detects_corruption() {
|
||||
let packet = build_hello_packet(5, 0, 42, &[]);
|
||||
|
|
|
|||
|
|
@ -663,3 +663,49 @@ Map: gm_bigcity_improved_rp / Players…» — значения сходятся
|
|||
**Статус сетевого слоя:** заголовок пакета, реассемблер фрагментов, connect,
|
||||
hello и `SV_ServerInfo` — по эталону подтверждены. Осталось разобрать
|
||||
multi-fragment переносы (send tables, string tables), сжатие и снапшоты.
|
||||
|
||||
### 13.12. Source.NET — источник истины, а не pcap (урок процесса)
|
||||
|
||||
Замечание «у нас есть исходники Source.NET, зачем мы реверсем» — справедливое,
|
||||
и оно указывает на потерянное время. Правильный порядок: **сначала читать
|
||||
`Source.Common/Networking/Protocol.cs` и `Source.Engine/NetChannel.cs`**,
|
||||
и лишь потом сверять с эталонным pcap.
|
||||
|
||||
Что дало чтение исходников за секунды (после часа перебора по захвату):
|
||||
|
||||
```csharp
|
||||
// Source.Common/Networking/Protocol.cs
|
||||
FRAGMENT_BITS = 12; FRAGMENT_SIZE = 1 << 12; // 4096, а НЕ 254
|
||||
MAX_FILE_SIZE_BITS = 30; // startFragment = 30-12 = 18 бит
|
||||
NETMSG_TYPE_BITS = 6; MAX_PAYLOAD = 288000;
|
||||
MAX_ROUTABLE_PAYLOAD = 1260;
|
||||
|
||||
// Source.Common/Networking/IConnectionlessPacketHandler.cs
|
||||
public enum PacketFlag {
|
||||
Reliable = 1<<0, Compressed = 1<<1, Encrypted = 1<<2,
|
||||
Split = 1<<3, Choked = 1<<4, Challenge = 1<<5
|
||||
}
|
||||
```
|
||||
|
||||
**Найденный баг.** У нас было `PACKET_FLAG_CHOKED = 1<<3` — это на самом деле
|
||||
`Split`. Значит в пакетах сервера с выставленным битом 3 мы читали лишний байт
|
||||
«choked», и `reliable_state` вместе с `challenge` уезжали на 8 бит — заголовок
|
||||
такого пакета разбирался неверно целиком. Исправлено на `Choked = 1<<4` +
|
||||
`Split = 1<<3`, закрыто тестами `packet_flags_match_specification` и
|
||||
`header_with_choked_flag_has_extra_byte`.
|
||||
|
||||
**Про порядок бит.** `Source.Common/Bitbuffers/bf_read.cs:752`
|
||||
(`ReadUBitLong`) — `dw1 = word >> startBit; dw2 = word << (32 - startBit)` —
|
||||
это в точности тот алгоритм, что я дизассемблировал из `engine_srv.so`:
|
||||
значения собираются **LSB-first**. Подтверждено дважды.
|
||||
|
||||
**Про сжатие в single-block.** Source.NET читает бит сжатия перед длиной,
|
||||
но эталон однозначно показывает 23-битный заголовок (4+2+1+VarInt32) без него.
|
||||
Похоже, GMod сигналит сжатие флагом пакета, а не фрагмента; наша правка
|
||||
опирается на измерение, источник тут расходится.
|
||||
|
||||
**Что уже совпадало с исходником** (значит, брали правильно):
|
||||
`FRAGMENT_SIZE = 1<<12 = 4096`, `FRAGMENT_OFFSET_BITS = 18`,
|
||||
`MAX_FRAGMENTS_PER_PACKET_BITS = 5`, `NETMSG_TYPE_BITS = 6`,
|
||||
`IN_RELIABLE_STATE_BITS = 16`, `SubChannel.MAX_BITS = 4` (совпало с дизасмом),
|
||||
порядок `net.SetConVar` → `net.SignonState(CONNECTED, -1)`.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue