320 lines
7.3 KiB
Go
320 lines
7.3 KiB
Go
|
|
// Package netproto 网络协议 v1 编解码(网络协议.md §1–§3)。
|
|||
|
|
//
|
|||
|
|
// 帧格式:长度 u32(大端)| 消息ID u16 | 压缩标志 u8 | payload;
|
|||
|
|
// 整数:定长大端 / 变长 varint(LEB128);字符串:varint 长度 + UTF-8。
|
|||
|
|
package netproto
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/binary"
|
|||
|
|
"errors"
|
|||
|
|
"fmt"
|
|||
|
|
"math"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 消息 ID(网络协议.md §3 消息目录)。
|
|||
|
|
const (
|
|||
|
|
MsgHandshake = 0x01 // C→S 握手
|
|||
|
|
MsgLoginRequest = 0x02 // C→S 登录
|
|||
|
|
MsgLoginResponse = 0x03 // S→C 登录结果
|
|||
|
|
MsgChunkData = 0x04 // S→C 区块数据
|
|||
|
|
MsgBlockChange = 0x05 // S→C 方块变更
|
|||
|
|
MsgPlayerMove = 0x06 // C→S 玩家移动
|
|||
|
|
MsgEntityMove = 0x07 // S→C 实体移动
|
|||
|
|
MsgChatMessage = 0x09 // 双向 聊天
|
|||
|
|
MsgInventorySync = 0x0A // 双向 背包
|
|||
|
|
MsgHeartbeat = 0x0E // 双向 心跳
|
|||
|
|
MsgDisconnect = 0x0F // 双向 断开
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 压缩标志(网络协议.md §1)。
|
|||
|
|
const (
|
|||
|
|
flagCompressed = 1 // payload 为 zstd 压缩块
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Frame 通用帧。
|
|||
|
|
type Frame struct {
|
|||
|
|
MsgID uint16
|
|||
|
|
Flags uint8
|
|||
|
|
Payload []byte
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MaxFrameSize 单帧上限 1 MiB(防御性,网络协议.md §1)。
|
|||
|
|
const MaxFrameSize = 1 << 20
|
|||
|
|
|
|||
|
|
// ErrIncomplete 半包错误:数据不足以组成完整帧(调用方应等待更多数据)。
|
|||
|
|
var ErrIncomplete = errors.New("netproto: 帧数据不完整")
|
|||
|
|
|
|||
|
|
// EncodeFrame 序列化一帧为字节流。
|
|||
|
|
// 帧头 7 字节:长度 u32(= 消息ID 2 + 标志 1 + 载荷,不含长度字段本身)+ 消息ID u16 + 压缩标志 u8。
|
|||
|
|
func EncodeFrame(f Frame) []byte {
|
|||
|
|
out := make([]byte, 7+len(f.Payload))
|
|||
|
|
binary.BigEndian.PutUint32(out[0:4], uint32(3+len(f.Payload)))
|
|||
|
|
binary.BigEndian.PutUint16(out[4:6], f.MsgID)
|
|||
|
|
out[6] = f.Flags
|
|||
|
|
copy(out[7:], f.Payload)
|
|||
|
|
return out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DecodeFrame 从字节流解析一帧,返回帧与剩余字节。
|
|||
|
|
// 数据不足时返回 ErrIncomplete(可用 errors.Is 判定半包,等待更多数据)。
|
|||
|
|
func DecodeFrame(data []byte) (Frame, []byte, error) {
|
|||
|
|
if len(data) < 4 {
|
|||
|
|
return Frame{}, nil, fmt.Errorf("%w(帧头不足 %d 字节)", ErrIncomplete, len(data))
|
|||
|
|
}
|
|||
|
|
length := binary.BigEndian.Uint32(data[0:4])
|
|||
|
|
if length > MaxFrameSize {
|
|||
|
|
return Frame{}, nil, fmt.Errorf("netproto: 帧长度 %d 超上限", length)
|
|||
|
|
}
|
|||
|
|
if int(length)+4 > len(data) {
|
|||
|
|
return Frame{}, nil, fmt.Errorf("%w(需 %d 字节,剩 %d)", ErrIncomplete, length+4, len(data))
|
|||
|
|
}
|
|||
|
|
f := Frame{
|
|||
|
|
MsgID: binary.BigEndian.Uint16(data[4:6]),
|
|||
|
|
Flags: data[6],
|
|||
|
|
Payload: data[7 : 4+int(length)],
|
|||
|
|
}
|
|||
|
|
return f, data[4+int(length):], nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---- 基础读写器(网络协议.md §2 序列化规则)----
|
|||
|
|
|
|||
|
|
// Writer 字节写入器。
|
|||
|
|
type Writer struct{ B []byte }
|
|||
|
|
|
|||
|
|
// U8 写无符号 8 位。
|
|||
|
|
func (w *Writer) U8(v uint8) { w.B = append(w.B, v) }
|
|||
|
|
|
|||
|
|
// U16 写无符号 16 位(大端)。
|
|||
|
|
func (w *Writer) U16(v uint16) { w.B = binary.BigEndian.AppendUint16(w.B, v) }
|
|||
|
|
|
|||
|
|
// U32 写无符号 32 位(大端)。
|
|||
|
|
func (w *Writer) U32(v uint32) { w.B = binary.BigEndian.AppendUint32(w.B, v) }
|
|||
|
|
|
|||
|
|
// I32 写有符号 32 位(大端)。
|
|||
|
|
func (w *Writer) I32(v int32) { w.U32(uint32(v)) }
|
|||
|
|
|
|||
|
|
// F32 写 float32(位模式)。
|
|||
|
|
func (w *Writer) F32(v float32) { w.U32(math.Float32bits(v)) }
|
|||
|
|
|
|||
|
|
// Varint 写变长整数(LEB128,无符号)。
|
|||
|
|
func (w *Writer) Varint(v uint32) {
|
|||
|
|
for v >= 0x80 {
|
|||
|
|
w.B = append(w.B, byte(v)|0x80)
|
|||
|
|
v >>= 7
|
|||
|
|
}
|
|||
|
|
w.B = append(w.B, byte(v))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Str 写字符串(varint 长度 + UTF-8)。
|
|||
|
|
func (w *Writer) Str(s string) {
|
|||
|
|
w.Varint(uint32(len(s)))
|
|||
|
|
w.B = append(w.B, s...)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Bytes 写字节串(varint 长度 + 数据)。
|
|||
|
|
func (w *Writer) Bytes(b []byte) {
|
|||
|
|
w.Varint(uint32(len(b)))
|
|||
|
|
w.B = append(w.B, b...)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Reader 字节读取器。
|
|||
|
|
type Reader struct {
|
|||
|
|
B []byte
|
|||
|
|
Off int
|
|||
|
|
Err error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewReader 创建读取器。
|
|||
|
|
func NewReader(b []byte) *Reader { return &Reader{B: b} }
|
|||
|
|
|
|||
|
|
// U8 读无符号 8 位。
|
|||
|
|
func (r *Reader) U8() uint8 {
|
|||
|
|
if r.Err != nil {
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
if r.Off+1 > len(r.B) {
|
|||
|
|
r.Err = fmt.Errorf("netproto: 读取越界")
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
v := r.B[r.Off]
|
|||
|
|
r.Off++
|
|||
|
|
return v
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// U16 读无符号 16 位(大端)。
|
|||
|
|
func (r *Reader) U16() uint16 {
|
|||
|
|
if r.Err != nil {
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
if r.Off+2 > len(r.B) {
|
|||
|
|
r.Err = fmt.Errorf("netproto: 读取越界")
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
v := binary.BigEndian.Uint16(r.B[r.Off:])
|
|||
|
|
r.Off += 2
|
|||
|
|
return v
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// U32 读无符号 32 位(大端)。
|
|||
|
|
func (r *Reader) U32() uint32 {
|
|||
|
|
if r.Err != nil {
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
if r.Off+4 > len(r.B) {
|
|||
|
|
r.Err = fmt.Errorf("netproto: 读取越界")
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
v := binary.BigEndian.Uint32(r.B[r.Off:])
|
|||
|
|
r.Off += 4
|
|||
|
|
return v
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// I32 读有符号 32 位。
|
|||
|
|
func (r *Reader) I32() int32 { return int32(r.U32()) }
|
|||
|
|
|
|||
|
|
// F32 读 float32。
|
|||
|
|
func (r *Reader) F32() float32 { return math.Float32frombits(r.U32()) }
|
|||
|
|
|
|||
|
|
// Varint 读变长整数(LEB128)。
|
|||
|
|
func (r *Reader) Varint() uint32 {
|
|||
|
|
if r.Err != nil {
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
var v uint32
|
|||
|
|
for shift := 0; shift < 35; shift += 7 {
|
|||
|
|
if r.Off >= len(r.B) {
|
|||
|
|
r.Err = fmt.Errorf("netproto: varint 越界")
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
b := r.B[r.Off]
|
|||
|
|
r.Off++
|
|||
|
|
v |= uint32(b&0x7F) << shift
|
|||
|
|
if b&0x80 == 0 {
|
|||
|
|
return v
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
r.Err = fmt.Errorf("netproto: varint 超长")
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Str 读字符串(varint 长度 + UTF-8)。
|
|||
|
|
func (r *Reader) Str() string {
|
|||
|
|
n := r.Varint()
|
|||
|
|
if r.Err != nil {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
if n > MaxFrameSize || r.Off+int(n) > len(r.B) {
|
|||
|
|
r.Err = fmt.Errorf("netproto: 字符串越界(长度 %d)", n)
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
s := string(r.B[r.Off : r.Off+int(n)])
|
|||
|
|
r.Off += int(n)
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Bytes 读字节串。
|
|||
|
|
func (r *Reader) Bytes() []byte {
|
|||
|
|
n := r.Varint()
|
|||
|
|
if r.Err != nil {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
if n > MaxFrameSize || r.Off+int(n) > len(r.B) {
|
|||
|
|
r.Err = fmt.Errorf("netproto: 字节串越界(长度 %d)", n)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
b := r.B[r.Off : r.Off+int(n)]
|
|||
|
|
r.Off += int(n)
|
|||
|
|
return b
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---- 常用消息载荷(网络协议.md §3)----
|
|||
|
|
|
|||
|
|
// BlockChange 方块变更(0x05)。
|
|||
|
|
type BlockChange struct {
|
|||
|
|
X, Y, Z int32
|
|||
|
|
BlockID uint16
|
|||
|
|
Meta uint8
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Encode 编码方块变更。
|
|||
|
|
func (m BlockChange) Encode() []byte {
|
|||
|
|
w := &Writer{}
|
|||
|
|
w.I32(m.X)
|
|||
|
|
w.I32(m.Y)
|
|||
|
|
w.I32(m.Z)
|
|||
|
|
w.U16(m.BlockID)
|
|||
|
|
w.U8(m.Meta)
|
|||
|
|
return w.B
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DecodeBlockChange 解码方块变更。
|
|||
|
|
func DecodeBlockChange(b []byte) (BlockChange, error) {
|
|||
|
|
r := NewReader(b)
|
|||
|
|
m := BlockChange{
|
|||
|
|
X: r.I32(),
|
|||
|
|
Y: r.I32(),
|
|||
|
|
Z: r.I32(),
|
|||
|
|
BlockID: r.U16(),
|
|||
|
|
Meta: r.U8(),
|
|||
|
|
}
|
|||
|
|
return m, r.Err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PlayerMove 玩家移动(0x06,20Hz)。
|
|||
|
|
type PlayerMove struct {
|
|||
|
|
X, Y, Z float32
|
|||
|
|
Yaw, Pitch float32
|
|||
|
|
OnGround bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Encode 编码玩家移动。
|
|||
|
|
func (m PlayerMove) Encode() []byte {
|
|||
|
|
w := &Writer{}
|
|||
|
|
w.F32(m.X)
|
|||
|
|
w.F32(m.Y)
|
|||
|
|
w.F32(m.Z)
|
|||
|
|
w.F32(m.Yaw)
|
|||
|
|
w.F32(m.Pitch)
|
|||
|
|
if m.OnGround {
|
|||
|
|
w.U8(1)
|
|||
|
|
} else {
|
|||
|
|
w.U8(0)
|
|||
|
|
}
|
|||
|
|
return w.B
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DecodePlayerMove 解码玩家移动。
|
|||
|
|
func DecodePlayerMove(b []byte) (PlayerMove, error) {
|
|||
|
|
r := NewReader(b)
|
|||
|
|
m := PlayerMove{
|
|||
|
|
X: r.F32(),
|
|||
|
|
Y: r.F32(),
|
|||
|
|
Z: r.F32(),
|
|||
|
|
Yaw: r.F32(),
|
|||
|
|
Pitch: r.F32(),
|
|||
|
|
}
|
|||
|
|
m.OnGround = r.U8() != 0
|
|||
|
|
return m, r.Err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Handshake 握手(0x01)。
|
|||
|
|
type Handshake struct {
|
|||
|
|
ProtocolVersion int
|
|||
|
|
ClientID string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Encode 编码握手。
|
|||
|
|
func (m Handshake) Encode() []byte {
|
|||
|
|
w := &Writer{}
|
|||
|
|
w.Varint(uint32(m.ProtocolVersion))
|
|||
|
|
w.Str(m.ClientID)
|
|||
|
|
return w.B
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DecodeHandshake 解码握手。
|
|||
|
|
func DecodeHandshake(b []byte) (Handshake, error) {
|
|||
|
|
r := NewReader(b)
|
|||
|
|
m := Handshake{ProtocolVersion: int(r.Varint()), ClientID: r.Str()}
|
|||
|
|
return m, r.Err
|
|||
|
|
}
|