Files
yml2213 36aadae90a 按 10.13 抓包对齐 WSS:host 自动解析、exp 回传、pingSignal 与兑换记录字段
- 新增 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 同步协议细节
2026-10-13 20:19:14 +08:00

536 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}
}