From a3b4e1b99492cfc5706d3fd1338adf76988ff68e Mon Sep 17 00:00:00 2001 From: wdvxdr Date: Fri, 24 Dec 2021 16:36:18 +0800 Subject: [PATCH] client: move parse packet logic to transport --- binary/reader.go | 8 ++ client/internal/network/response.go | 92 ++++++++++++++++++ client/internal/network/transport.go | 24 ----- client/network.go | 21 ++-- internal/packets/global.go | 139 --------------------------- 5 files changed, 114 insertions(+), 170 deletions(-) create mode 100644 client/internal/network/response.go diff --git a/binary/reader.go b/binary/reader.go index 05bb2d77..f11c608f 100644 --- a/binary/reader.go +++ b/binary/reader.go @@ -71,6 +71,10 @@ func (r *Reader) ReadString() string { return utils.B2S(data) } +func (r *Reader) ReadInt32Bytes() []byte { + return r.ReadBytes(int(r.ReadInt32() - 4)) +} + func (r *Reader) ReadStringShort() string { data := r.ReadBytes(int(r.ReadUInt16())) return utils.B2S(data) @@ -116,6 +120,10 @@ func (r *Reader) Len() int { return r.buf.Len() } +func (r *Reader) Index() int64 { + return r.buf.Size() +} + func (tlv TlvMap) Exists(key uint16) bool { _, ok := tlv[key] return ok diff --git a/client/internal/network/response.go b/client/internal/network/response.go new file mode 100644 index 00000000..ce911ed5 --- /dev/null +++ b/client/internal/network/response.go @@ -0,0 +1,92 @@ +package network + +import ( + "strconv" + + "github.com/pkg/errors" + + "github.com/Mrs4s/MiraiGo/binary" +) + +type Response struct { + Type RequestType + EncryptType EncryptType + SequenceID int32 + Uin int64 + CommandName string + Body []byte + + Message string + + // Request is the original request that obtained this response. + // Request *Request +} + +var ( + ErrSessionExpired = errors.New("session expired") + ErrPacketDropped = errors.New("packet dropped") + ErrInvalidPacketType = errors.New("invalid packet type") +) + +func (t *Transport) ReadResponse(head []byte) (*Response, error) { + resp := new(Response) + r := binary.NewReader(head) + resp.Type = RequestType(r.ReadInt32()) + if resp.Type != RequestTypeLogin && resp.Type != RequestTypeSimple { + return resp, ErrInvalidPacketType + } + resp.EncryptType = EncryptType(r.ReadByte()) + _ = r.ReadByte() // 0x00? + + resp.Uin, _ = strconv.ParseInt(r.ReadString(), 10, 64) + body := r.ReadAvailable() + switch resp.EncryptType { + case EncryptTypeNoEncrypt: + // nothing to do + case EncryptTypeD2Key: + body = binary.NewTeaCipher(t.Sig.D2Key).Decrypt(body) + case EncryptTypeEmptyKey: + body = binary.NewTeaCipher(emptyKey).Decrypt(body) + } + err := t.readSSOFrame(resp, body) + return resp, err +} + +func (t *Transport) readSSOFrame(resp *Response, payload []byte) error { + reader := binary.NewReader(payload) + headLen := reader.ReadInt32() + if headLen-4 > int32(reader.Len()) { + return errors.WithStack(ErrPacketDropped) + } + + head := binary.NewReader(reader.ReadBytes(int(headLen) - 4)) + resp.SequenceID = head.ReadInt32() + switch retCode := head.ReadInt32(); retCode { + case 0: + // ok + case -10008: + return errors.WithStack(ErrSessionExpired) + default: + return errors.Errorf("return code unsuccessful: %d", retCode) + } + resp.Message = head.ReadString() + resp.CommandName = head.ReadString() + if resp.CommandName == "Heartbeat.Alive" { + return nil + } + _ = head.ReadInt32Bytes() // session id + compressedFlag := head.ReadInt32() + + bodyLen := reader.ReadInt32() - 4 + body := reader.ReadAvailable() + if bodyLen > 0 && bodyLen < int32(len(body)) { + body = body[:bodyLen] + } + switch compressedFlag { + case 0, 8: + case 1: + body = binary.ZlibUncompress(body) + } + resp.Body = body + return nil +} diff --git a/client/internal/network/transport.go b/client/internal/network/transport.go index 6f4c234b..dd79a8d9 100644 --- a/client/internal/network/transport.go +++ b/client/internal/network/transport.go @@ -97,27 +97,3 @@ func (t *Transport) PackPacket(req *Request) []byte { w.WriteUInt32At(pos, uint32(w.Len())) return append([]byte(nil), w.Bytes()...) } - -func (t *Transport) parse(head []byte) *Request { - req := new(Request) - r := binary.NewReader(head) - req.Type = RequestType(r.ReadInt32()) - encryptType := EncryptType(r.ReadInt32()) - switch req.Type { - case RequestTypeLogin: - // req.Key = r.ReadBytes(int(encryptType)) - case RequestTypeSimple: - req.SequenceID = r.ReadInt32() - } - _ = r.ReadString() // req.Uin - body := r.ReadAvailable() - switch encryptType { - case EncryptTypeNoEncrypt: - req.Body = body - case EncryptTypeD2Key: - req.Body = binary.NewTeaCipher(t.Sig.D2Key).Decrypt(body) - case EncryptTypeEmptyKey: - req.Body = binary.NewTeaCipher(emptyKey).Decrypt(body) - } - return req -} diff --git a/client/network.go b/client/network.go index cadf4007..bab7565e 100644 --- a/client/network.go +++ b/client/network.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/Mrs4s/MiraiGo/client/internal/network" + "github.com/Mrs4s/MiraiGo/client/internal/oicq" "github.com/Mrs4s/MiraiGo/internal/packets" "github.com/Mrs4s/MiraiGo/message" "github.com/Mrs4s/MiraiGo/utils" @@ -291,10 +292,11 @@ func (c *QQClient) netLoop() { continue } data, _ := c.TCP.ReadBytes(int(l) - 4) - pkt, err := packets.ParseIncomingPacket(data, c.sig.D2Key) + resp, err := c.transport.ReadResponse(data) + // pkt, err := packets.ParseIncomingPacket(data, c.sig.D2Key) if err != nil { c.Error("parse incoming packet error: %v", err) - if errors.Is(err, packets.ErrSessionExpired) || errors.Is(err, packets.ErrPacketDropped) { + if errors.Is(err, network.ErrSessionExpired) || errors.Is(err, network.ErrPacketDropped) { c.Disconnect() go c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "session expired"}) continue @@ -305,20 +307,25 @@ func (c *QQClient) netLoop() { } continue } - if pkt.Flag2 == 2 { - m, err := c.oicq.Unmarshal(pkt.Payload) + if resp.EncryptType == network.EncryptTypeEmptyKey { + m, err := c.oicq.Unmarshal(resp.Body) if err != nil { c.Error("decrypt payload error: %v", err) - if errors.Is(err, packets.ErrUnknownFlag) { + if errors.Is(err, oicq.ErrUnknownFlag) { go c.quickReconnect() } continue } - pkt.Payload = m.Body + resp.Body = m.Body } errCount = 0 - c.Debug("rev pkt: %v seq: %v", pkt.CommandName, pkt.SequenceId) + c.Debug("rev pkt: %v seq: %v", resp.CommandName, resp.SequenceID) c.stat.PacketReceived.Add(1) + pkt := &packets.IncomingPacket{ + SequenceId: uint16(resp.SequenceID), + CommandName: resp.CommandName, + Payload: resp.Body, + } go func(pkt *packets.IncomingPacket) { defer func() { if pan := recover(); pan != nil { diff --git a/internal/packets/global.go b/internal/packets/global.go index 8ea50ab3..32f3ce46 100644 --- a/internal/packets/global.go +++ b/internal/packets/global.go @@ -1,26 +1,9 @@ package packets import ( - "strconv" - - "github.com/pkg/errors" - "github.com/Mrs4s/MiraiGo/binary" ) -var ( - ErrUnknownFlag = errors.New("unknown flag") - ErrInvalidPayload = errors.New("invalid payload") - ErrDecryptFailed = errors.New("decrypt failed") - ErrSessionExpired = errors.New("session expired") - ErrPacketDropped = errors.New("packet dropped") -) - -type ISendingPacket interface { - CommandId() uint16 - Writer() *binary.Writer -} - type IncomingPacket struct { SequenceId uint16 Flag2 byte @@ -45,125 +28,3 @@ func BuildCode2DRequestPacket(seq uint32, j uint64, cmd uint16, bodyFunc func(wr w.WriteUInt16At(pos, uint16(w.Len())) }) } - -func ParseIncomingPacket(payload, d2key []byte) (*IncomingPacket, error) { - if len(payload) < 6 { - return nil, errors.WithStack(ErrInvalidPayload) - } - reader := binary.NewReader(payload) - flag1 := reader.ReadInt32() - flag2 := reader.ReadByte() - if reader.ReadByte() != 0 { // flag3 - // return nil, errors.WithStack(ErrUnknownFlag) - } - reader.ReadString() // uin string - decrypted := func() (data []byte) { - switch flag2 { - case 0: - return reader.ReadAvailable() - case 1: - d2 := binary.NewTeaCipher(d2key) - return d2.Decrypt(reader.ReadAvailable()) - case 2: - z16 := binary.NewTeaCipher(make([]byte, 16)) - return z16.Decrypt(reader.ReadAvailable()) - } - return nil - }() - if len(decrypted) == 0 { - return nil, errors.WithStack(ErrDecryptFailed) - } - if flag1 != 0x0A && flag1 != 0x0B { - return nil, errors.WithStack(ErrDecryptFailed) - } - return parseSsoFrame(decrypted, flag2) -} - -func parseSsoFrame(payload []byte, flag2 byte) (*IncomingPacket, error) { - reader := binary.NewReader(payload) - headLen := reader.ReadInt32() - if headLen-4 > int32(reader.Len()) { - return nil, errors.WithStack(ErrPacketDropped) - } - head := binary.NewReader(reader.ReadBytes(int(headLen) - 4)) - seqID := head.ReadInt32() - retCode := head.ReadInt32() - if retCode != 0 { - if retCode == -10008 { - return nil, errors.WithStack(ErrSessionExpired) - } - return nil, errors.New("return code unsuccessful: " + strconv.FormatInt(int64(retCode), 10)) - } - head.ReadBytes(int(head.ReadInt32()) - 4) // extra data - commandName := head.ReadString() - sessionID := head.ReadBytes(int(head.ReadInt32()) - 4) - if commandName == "Heartbeat.Alive" { - return &IncomingPacket{ - SequenceId: uint16(seqID), - Flag2: flag2, - CommandName: commandName, - SessionId: sessionID, - Payload: []byte{}, - }, nil - } - compressedFlag := head.ReadInt32() - reader.ReadInt32() - packet := func() []byte { - if compressedFlag == 0 { - return reader.ReadAvailable() - } - if compressedFlag == 1 { - return binary.ZlibUncompress(reader.ReadAvailable()) - } - if compressedFlag == 8 { - return reader.ReadAvailable() - } - return nil - }() - return &IncomingPacket{ - SequenceId: uint16(seqID), - Flag2: flag2, - CommandName: commandName, - SessionId: sessionID, - Payload: packet, - }, nil -} - -func (pkt *IncomingPacket) DecryptPayload(ecdhShareKey, random, sessionKey []byte) ([]byte, error) { - reader := binary.NewReader(pkt.Payload) - if flag := reader.ReadByte(); flag != 2 { - return nil, ErrUnknownFlag - } - reader.ReadBytes(2) - reader.ReadBytes(2) - reader.ReadUInt16() - reader.ReadUInt16() - reader.ReadInt32() - encryptType := reader.ReadUInt16() - reader.ReadByte() - if encryptType == 0 { - data := func() (decrypted []byte) { - d := reader.ReadBytes(reader.Len() - 1) - defer func() { - if pan := recover(); pan != nil { - tea := binary.NewTeaCipher(random) - decrypted = tea.Decrypt(d) - } - }() - tea := binary.NewTeaCipher(ecdhShareKey) - decrypted = tea.Decrypt(d) - return - }() - return data, nil - } - if encryptType == 3 { - return func() []byte { - d := reader.ReadBytes(reader.Len() - 1) - return binary.NewTeaCipher(sessionKey).Decrypt(d) - }(), nil - } - if encryptType == 4 { - panic("todo") - } - return nil, errors.WithStack(ErrUnknownFlag) -}