- 新增 launch.queryHttpDns:类型定义 + HTTPClient.QueryHttpDns + WSSClient.resolveHost(TTL 缓存、多 host 轮询、失败回退默认 host);Host 留空或等于历史默认值即视为自动解析 - WSS 握手 baseinfo 补 tag5=exp 与 tag10 空 customHeaders;UpdateUserExpsReq(cmd23) 改为回传 getConfig 的版本 map(缓存供重连复用) - 新增 launch.pingSignal:HTTP 预热改走 pingSignal,WSS 预热在建连后探测业务通道(失败仅记日志) - UserPrizeInfo 补 tag12 orderId/tag14 图标/tag16 prizeId/tag17 shopItemId/tag19 itemId/tag20 ext/tag21 exchangeDate,前端复盘表加订单号列 - taf 补 WriteListString/ReadListString/ReadMapStringInt64/ReadMapStringStruct - 新增 internal/huya/launch_test.go:用抓包真实报文做字节级夹具(baseinfo 122B、queryHttpDns 请求 93B、记录 308B 等) - CLI -wss-host 默认改为留空;GUI 设置支持留空并给出占位提示;README/WSS_NOTES 同步协议细节
536 lines
12 KiB
Go
536 lines
12 KiB
Go
package taf
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/binary"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"math"
|
||
)
|
||
|
||
// Reader 负责从 TAF/JCE 字节流中按 tag 顺序读取字段。
|
||
type Reader struct {
|
||
buf *bytes.Reader
|
||
}
|
||
|
||
func NewReader(data []byte) *Reader {
|
||
return &Reader{buf: bytes.NewReader(data)}
|
||
}
|
||
|
||
func (r *Reader) Position() int64 {
|
||
pos, _ := r.buf.Seek(0, io.SeekCurrent)
|
||
return pos
|
||
}
|
||
|
||
func (r *Reader) Seek(pos int64) error {
|
||
_, err := r.buf.Seek(pos, io.SeekStart)
|
||
return err
|
||
}
|
||
|
||
func (r *Reader) ReadHead() (int, Type, error) {
|
||
b, err := r.buf.ReadByte()
|
||
if err != nil {
|
||
return 0, 0, err
|
||
}
|
||
tag := int((b >> 4) & 0x0f)
|
||
typ := Type(b & 0x0f)
|
||
if tag == 15 {
|
||
ext, err := r.buf.ReadByte()
|
||
if err != nil {
|
||
return 0, 0, err
|
||
}
|
||
tag = int(ext)
|
||
}
|
||
return tag, typ, nil
|
||
}
|
||
|
||
func (r *Reader) PeekHead() (int, Type, error) {
|
||
pos := r.Position()
|
||
tag, typ, err := r.ReadHead()
|
||
_ = r.Seek(pos)
|
||
return tag, typ, err
|
||
}
|
||
|
||
func (r *Reader) ReadIntValue(typ Type) (int64, error) {
|
||
switch typ {
|
||
case TypeZero:
|
||
return 0, nil
|
||
case TypeInt8:
|
||
b, err := r.buf.ReadByte()
|
||
return int64(int8(b)), err
|
||
case TypeInt16:
|
||
var b [2]byte
|
||
if _, err := io.ReadFull(r.buf, b[:]); err != nil {
|
||
return 0, err
|
||
}
|
||
return int64(int16(binary.BigEndian.Uint16(b[:]))), nil
|
||
case TypeInt32:
|
||
var b [4]byte
|
||
if _, err := io.ReadFull(r.buf, b[:]); err != nil {
|
||
return 0, err
|
||
}
|
||
return int64(int32(binary.BigEndian.Uint32(b[:]))), nil
|
||
case TypeInt64:
|
||
var b [8]byte
|
||
if _, err := io.ReadFull(r.buf, b[:]); err != nil {
|
||
return 0, err
|
||
}
|
||
return int64(binary.BigEndian.Uint64(b[:])), nil
|
||
default:
|
||
return 0, fmt.Errorf("taf: 期望整数类型,实际 0x%02x", byte(typ))
|
||
}
|
||
}
|
||
|
||
func (r *Reader) ReadStringValue(typ Type) (string, error) {
|
||
var n uint32
|
||
switch typ {
|
||
case TypeString1:
|
||
b, err := r.buf.ReadByte()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
n = uint32(b)
|
||
case TypeString4:
|
||
var b [4]byte
|
||
if _, err := io.ReadFull(r.buf, b[:]); err != nil {
|
||
return "", err
|
||
}
|
||
n = binary.BigEndian.Uint32(b[:])
|
||
default:
|
||
return "", fmt.Errorf("taf: 期望字符串类型,实际 0x%02x", byte(typ))
|
||
}
|
||
raw := make([]byte, n)
|
||
if _, err := io.ReadFull(r.buf, raw); err != nil {
|
||
return "", err
|
||
}
|
||
return string(raw), nil
|
||
}
|
||
|
||
func (r *Reader) ReadBytesValue(typ Type) ([]byte, error) {
|
||
if typ != TypeSimpleList {
|
||
return nil, fmt.Errorf("taf: 期望 bytes,实际 0x%02x", byte(typ))
|
||
}
|
||
_, itemType, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if itemType != TypeInt8 {
|
||
return nil, fmt.Errorf("taf: bytes 元素类型不是 int8: 0x%02x", byte(itemType))
|
||
}
|
||
n, err := r.readLength()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if n < 0 || n > math.MaxInt32 {
|
||
return nil, fmt.Errorf("taf: bytes 长度异常: %d", n)
|
||
}
|
||
raw := make([]byte, int(n))
|
||
_, err = io.ReadFull(r.buf, raw)
|
||
return raw, err
|
||
}
|
||
|
||
func (r *Reader) Skip(typ Type) error {
|
||
switch typ {
|
||
case TypeZero:
|
||
return nil
|
||
case TypeInt8:
|
||
_, err := r.buf.ReadByte()
|
||
return err
|
||
case TypeInt16:
|
||
_, err := r.buf.Seek(2, io.SeekCurrent)
|
||
return err
|
||
case TypeInt32, TypeFloat:
|
||
_, err := r.buf.Seek(4, io.SeekCurrent)
|
||
return err
|
||
case TypeInt64, TypeDouble:
|
||
_, err := r.buf.Seek(8, io.SeekCurrent)
|
||
return err
|
||
case TypeString1:
|
||
b, err := r.buf.ReadByte()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
_, err = r.buf.Seek(int64(b), io.SeekCurrent)
|
||
return err
|
||
case TypeString4:
|
||
var b [4]byte
|
||
if _, err := io.ReadFull(r.buf, b[:]); err != nil {
|
||
return err
|
||
}
|
||
_, err := r.buf.Seek(int64(binary.BigEndian.Uint32(b[:])), io.SeekCurrent)
|
||
return err
|
||
case TypeMap:
|
||
n, err := r.readLength()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
for i := int64(0); i < n; i++ {
|
||
if _, kt, err := r.ReadHead(); err != nil {
|
||
return err
|
||
} else if err := r.Skip(kt); err != nil {
|
||
return err
|
||
}
|
||
if _, vt, err := r.ReadHead(); err != nil {
|
||
return err
|
||
} else if err := r.Skip(vt); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
case TypeList:
|
||
n, err := r.readLength()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
for i := int64(0); i < n; i++ {
|
||
if _, it, err := r.ReadHead(); err != nil {
|
||
return err
|
||
} else if err := r.Skip(it); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
case TypeStructBegin:
|
||
return r.SkipToStructEnd()
|
||
case TypeStructEnd:
|
||
return nil
|
||
case TypeSimpleList:
|
||
_, itemType, err := r.ReadHead()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if itemType != TypeInt8 {
|
||
return fmt.Errorf("taf: simple list 元素类型异常: 0x%02x", byte(itemType))
|
||
}
|
||
n, err := r.readLength()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
_, err = r.buf.Seek(n, io.SeekCurrent)
|
||
return err
|
||
default:
|
||
return fmt.Errorf("taf: 未知字段类型 0x%02x", byte(typ))
|
||
}
|
||
}
|
||
|
||
func (r *Reader) readLength() (int64, error) {
|
||
_, typ, err := r.ReadHead()
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return r.ReadIntValue(typ)
|
||
}
|
||
|
||
func (r *Reader) findTag(target int, required bool) (Type, bool, error) {
|
||
for {
|
||
pos := r.Position()
|
||
tag, typ, err := r.ReadHead()
|
||
if err != nil {
|
||
if errors.Is(err, io.EOF) && !required {
|
||
return 0, false, nil
|
||
}
|
||
return 0, false, err
|
||
}
|
||
if typ == TypeStructEnd {
|
||
_ = r.Seek(pos)
|
||
if required {
|
||
return 0, false, fmt.Errorf("taf: 未找到 tag=%d,已到结构尾", target)
|
||
}
|
||
return 0, false, nil
|
||
}
|
||
if tag == target {
|
||
return typ, true, nil
|
||
}
|
||
if tag > target {
|
||
_ = r.Seek(pos)
|
||
if required {
|
||
return 0, false, fmt.Errorf("taf: 未找到 tag=%d,遇到 tag=%d", target, tag)
|
||
}
|
||
return 0, false, nil
|
||
}
|
||
if err := r.Skip(typ); err != nil {
|
||
return 0, false, err
|
||
}
|
||
}
|
||
}
|
||
|
||
func (r *Reader) ReadInt64(tag int, required bool, def int64) (int64, error) {
|
||
typ, ok, err := r.findTag(tag, required)
|
||
if err != nil || !ok {
|
||
return def, err
|
||
}
|
||
return r.ReadIntValue(typ)
|
||
}
|
||
|
||
func (r *Reader) ReadInt32(tag int, required bool, def int32) (int32, error) {
|
||
value, err := r.ReadInt64(tag, required, int64(def))
|
||
return int32(value), err
|
||
}
|
||
|
||
func (r *Reader) ReadString(tag int, required bool, def string) (string, error) {
|
||
typ, ok, err := r.findTag(tag, required)
|
||
if err != nil || !ok {
|
||
return def, err
|
||
}
|
||
return r.ReadStringValue(typ)
|
||
}
|
||
|
||
func (r *Reader) ReadBytes(tag int, required bool, def []byte) ([]byte, error) {
|
||
typ, ok, err := r.findTag(tag, required)
|
||
if err != nil || !ok {
|
||
return def, err
|
||
}
|
||
return r.ReadBytesValue(typ)
|
||
}
|
||
|
||
func (r *Reader) ReadMapStringString(tag int, required bool) (map[string]string, error) {
|
||
typ, ok, err := r.findTag(tag, required)
|
||
if err != nil || !ok {
|
||
return map[string]string{}, err
|
||
}
|
||
if typ != TypeMap {
|
||
return nil, fmt.Errorf("taf: tag=%d 不是 map: 0x%02x", tag, byte(typ))
|
||
}
|
||
n, err := r.readLength()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out := make(map[string]string, n)
|
||
for i := int64(0); i < n; i++ {
|
||
_, kt, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
k, err := r.ReadStringValue(kt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
_, vt, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
v, err := r.ReadStringValue(vt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out[k] = v
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
// ReadMapStringInt64 读取 map<string,int64>,供 launch.pingSignal 这类响应使用。
|
||
func (r *Reader) ReadMapStringInt64(tag int, required bool) (map[string]int64, error) {
|
||
typ, ok, err := r.findTag(tag, required)
|
||
if err != nil || !ok {
|
||
return map[string]int64{}, err
|
||
}
|
||
if typ != TypeMap {
|
||
return nil, fmt.Errorf("taf: tag=%d 不是 map: 0x%02x", tag, byte(typ))
|
||
}
|
||
n, err := r.readLength()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out := make(map[string]int64, n)
|
||
for i := int64(0); i < n; i++ {
|
||
_, kt, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
k, err := r.ReadStringValue(kt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
_, vt, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
v, err := r.ReadIntValue(vt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out[k] = v
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
// ReadMapStringStruct 读取 map<string, struct>,value 由 factory 创建后自行 ReadFrom。
|
||
func (r *Reader) ReadMapStringStruct(tag int, required bool, factory func() Unmarshaler) (map[string]Unmarshaler, error) {
|
||
typ, ok, err := r.findTag(tag, required)
|
||
if err != nil || !ok {
|
||
return map[string]Unmarshaler{}, err
|
||
}
|
||
if typ != TypeMap {
|
||
return nil, fmt.Errorf("taf: tag=%d 不是 map: 0x%02x", tag, byte(typ))
|
||
}
|
||
n, err := r.readLength()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out := make(map[string]Unmarshaler, n)
|
||
for i := int64(0); i < n; i++ {
|
||
_, kt, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
k, err := r.ReadStringValue(kt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
_, vt, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if vt != TypeStructBegin {
|
||
if err := r.Skip(vt); err != nil {
|
||
return nil, err
|
||
}
|
||
continue
|
||
}
|
||
obj := factory()
|
||
if err := obj.ReadFrom(r); err != nil {
|
||
return nil, err
|
||
}
|
||
if err := r.SkipToStructEnd(); err != nil {
|
||
return nil, err
|
||
}
|
||
out[k] = obj
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
// ReadListString 读取 list<string>;非字符串元素会被跳过而不是报错。
|
||
func (r *Reader) ReadListString(tag int, required bool) ([]string, error) {
|
||
typ, ok, err := r.findTag(tag, required)
|
||
if err != nil || !ok {
|
||
return nil, err
|
||
}
|
||
if typ != TypeList {
|
||
return nil, fmt.Errorf("taf: tag=%d 不是 list: 0x%02x", tag, byte(typ))
|
||
}
|
||
n, err := r.readLength()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out := make([]string, 0, n)
|
||
for i := int64(0); i < n; i++ {
|
||
_, it, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if it != TypeString1 && it != TypeString4 {
|
||
if err := r.Skip(it); err != nil {
|
||
return nil, err
|
||
}
|
||
continue
|
||
}
|
||
value, err := r.ReadStringValue(it)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out = append(out, value)
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func (r *Reader) ReadMapStringBytes(tag int, required bool) (map[string][]byte, error) {
|
||
typ, ok, err := r.findTag(tag, required)
|
||
if err != nil || !ok {
|
||
return map[string][]byte{}, err
|
||
}
|
||
if typ != TypeMap {
|
||
return nil, fmt.Errorf("taf: tag=%d 不是 map: 0x%02x", tag, byte(typ))
|
||
}
|
||
n, err := r.readLength()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out := make(map[string][]byte, n)
|
||
for i := int64(0); i < n; i++ {
|
||
_, kt, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
k, err := r.ReadStringValue(kt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
_, vt, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
v, err := r.ReadBytesValue(vt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out[k] = v
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func (r *Reader) ReadStruct(tag int, required bool, obj Unmarshaler) (bool, error) {
|
||
typ, ok, err := r.findTag(tag, required)
|
||
if err != nil || !ok {
|
||
return false, err
|
||
}
|
||
if typ != TypeStructBegin {
|
||
return false, fmt.Errorf("taf: tag=%d 不是结构体: 0x%02x", tag, byte(typ))
|
||
}
|
||
if err := obj.ReadFrom(r); err != nil {
|
||
return false, err
|
||
}
|
||
return true, r.SkipToStructEnd()
|
||
}
|
||
|
||
func (r *Reader) ReadListStruct(tag int, required bool, factory func() Unmarshaler) ([]Unmarshaler, error) {
|
||
typ, ok, err := r.findTag(tag, required)
|
||
if err != nil || !ok {
|
||
return nil, err
|
||
}
|
||
if typ != TypeList {
|
||
return nil, fmt.Errorf("taf: tag=%d 不是 list: 0x%02x", tag, byte(typ))
|
||
}
|
||
n, err := r.readLength()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out := make([]Unmarshaler, 0, n)
|
||
for i := int64(0); i < n; i++ {
|
||
_, it, err := r.ReadHead()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if it != TypeStructBegin {
|
||
if err := r.Skip(it); err != nil {
|
||
return nil, err
|
||
}
|
||
continue
|
||
}
|
||
obj := factory()
|
||
if err := obj.ReadFrom(r); err != nil {
|
||
return nil, err
|
||
}
|
||
if err := r.SkipToStructEnd(); err != nil {
|
||
return nil, err
|
||
}
|
||
out = append(out, obj)
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
func (r *Reader) SkipToStructEnd() error {
|
||
for {
|
||
_, typ, err := r.ReadHead()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if typ == TypeStructEnd {
|
||
return nil
|
||
}
|
||
if err := r.Skip(typ); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|