1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-04 11:07:40 +08:00

refactor: move highway.go to client/internal/highway

This commit is contained in:
wdvxdr 2021-12-15 19:12:43 +08:00
parent ed979508cf
commit b398cec6a5
No known key found for this signature in database
GPG Key ID: 703F8C071DE7A1B6
19 changed files with 716 additions and 572 deletions

View File

@ -63,7 +63,8 @@ var req = RequestDataVersion2{
"5": { "5": {
"123": []byte(`123`), "123": []byte(`123`),
}, },
}} },
}
func TestRequestDataVersion2_ReadFrom(t *testing.T) { func TestRequestDataVersion2_ReadFrom(t *testing.T) {
// todo(wdv): fuzz test // todo(wdv): fuzz test

View File

@ -9,7 +9,7 @@ import (
var globalBytes []byte var globalBytes []byte
func BenchmarkJceWriter_WriteMap(b *testing.B) { func BenchmarkJceWriter_WriteMap(b *testing.B) {
var x = globalBytes x := globalBytes
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
w := NewJceWriter() w := NewJceWriter()
w.writeMapStrMapStrBytes(req.Map, 0) w.writeMapStrMapStrBytes(req.Map, 0)
@ -39,7 +39,7 @@ var reqPacket1 = &RequestPacket{
} }
func BenchmarkJceWriter_WriteJceStructRaw(b *testing.B) { func BenchmarkJceWriter_WriteJceStructRaw(b *testing.B) {
var x = globalBytes x := globalBytes
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = reqPacket1.ToBytes() _ = reqPacket1.ToBytes()
} }

View File

@ -15,6 +15,7 @@ import (
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/binary/jce" "github.com/Mrs4s/MiraiGo/binary/jce"
"github.com/Mrs4s/MiraiGo/client/internal/highway"
"github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/internal/crypto" "github.com/Mrs4s/MiraiGo/internal/crypto"
"github.com/Mrs4s/MiraiGo/internal/packets" "github.com/Mrs4s/MiraiGo/internal/packets"
@ -84,13 +85,12 @@ type QQClient struct {
// session info // session info
qwebSeq int64 qwebSeq int64
sigInfo *loginSigInfo sigInfo *loginSigInfo
bigDataSession *bigDataSessionInfo highwaySession *highway.Session
dpwd []byte dpwd []byte
timeDiff int64 timeDiff int64
pwdFlag bool pwdFlag bool
// address // address
srvSsoAddrs []string
otherSrvAddrs []string otherSrvAddrs []string
fileStorageInfo *jce.FileStoragePushFSSvcList fileStorageInfo *jce.FileStoragePushFSSvcList
@ -270,6 +270,7 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient {
func (c *QQClient) UseDevice(info *DeviceInfo) { func (c *QQClient) UseDevice(info *DeviceInfo) {
c.version = genVersionInfo(info.Protocol) c.version = genVersionInfo(info.Protocol)
c.highwaySession = highway.NewSession(int32(c.version.AppId), c.Uin)
c.ksid = []byte(fmt.Sprintf("|%s|A8.2.7.27f6ea96", info.IMEI)) c.ksid = []byte(fmt.Sprintf("|%s|A8.2.7.27f6ea96", info.IMEI))
c.deviceInfo = info c.deviceInfo = info
} }

View File

@ -346,14 +346,12 @@ func decodePushReqPacket(c *QQClient, _ *incomingPacketInfo, payload []byte) (in
c.fileStorageInfo = list c.fileStorageInfo = list
rsp := cmd0x6ff.C501RspBody{} rsp := cmd0x6ff.C501RspBody{}
if err := proto.Unmarshal(list.BigDataChannel.PbBuf, &rsp); err == nil && rsp.RspBody != nil { if err := proto.Unmarshal(list.BigDataChannel.PbBuf, &rsp); err == nil && rsp.RspBody != nil {
c.bigDataSession = &bigDataSessionInfo{ c.highwaySession.SigSession = rsp.RspBody.SigSession
SigSession: rsp.RspBody.SigSession, c.highwaySession.SessionKey = rsp.RspBody.SessionKey
SessionKey: rsp.RspBody.SessionKey,
}
for _, srv := range rsp.RspBody.Addrs { for _, srv := range rsp.RspBody.Addrs {
if srv.GetServiceType() == 10 { if srv.GetServiceType() == 10 {
for _, addr := range srv.Addrs { for _, addr := range srv.Addrs {
c.srvSsoAddrs = append(c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr.GetIp()), addr.GetPort())) c.highwaySession.AppendAddr(addr.GetIp(), addr.GetPort())
} }
} }
if srv.GetServiceType() == 21 { if srv.GetServiceType() == 21 {

View File

@ -11,6 +11,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/Mrs4s/MiraiGo/client/internal/highway"
"github.com/Mrs4s/MiraiGo/client/pb/exciting" "github.com/Mrs4s/MiraiGo/client/pb/exciting"
"github.com/Mrs4s/MiraiGo/client/pb/oidb" "github.com/Mrs4s/MiraiGo/client/pb/oidb"
"github.com/Mrs4s/MiraiGo/internal/packets" "github.com/Mrs4s/MiraiGo/internal/packets"
@ -225,11 +226,18 @@ func (fs *GroupFileSystem) UploadFile(p, name, folderId string) error {
}, },
Unknown3: proto.Int32(0), Unknown3: proto.Int32(0),
}) })
if _, err = fs.client.excitingUploadStream(file, 71, fs.client.bigDataSession.SigSession, ext); err != nil { client := fs.client
input := highway.ExcitingInput{
CommandID: 71,
Body: file,
Ticket: fs.client.highwaySession.SigSession,
Ext: ext,
}
if _, err = fs.client.highwaySession.UploadExciting(input); err != nil {
return errors.Wrap(err, "upload failed") return errors.Wrap(err, "upload failed")
} }
_, pkt := fs.client.buildGroupFileFeedsRequest(fs.GroupCode, rsp.GetFileId(), rsp.GetBusId(), rand.Int31()) _, pkt := client.buildGroupFileFeedsRequest(fs.GroupCode, rsp.GetFileId(), rsp.GetBusId(), rand.Int31())
return fs.client.sendPacket(pkt) return client.sendPacket(pkt)
} }
func (fs *GroupFileSystem) GetDownloadUrl(file *GroupFile) string { func (fs *GroupFileSystem) GetDownloadUrl(file *GroupFile) string {

View File

@ -1,9 +1,11 @@
package client package client
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/rand" "math/rand"
"net/http"
"net/url" "net/url"
"sort" "sort"
"strings" "strings"
@ -260,6 +262,20 @@ func decodeGroupInfoResponse(c *QQClient, _ *incomingPacketInfo, payload []byte)
}, nil }, nil
} }
func (c *QQClient) uploadGroupHeadPortrait(groupCode int64, img []byte) error {
url := fmt.Sprintf("http://htdata3.qq.com/cgi-bin/httpconn?htcmd=0x6ff0072&ver=5520&ukey=%v&range=0&uin=%v&seq=23&groupuin=%v&filetype=3&imagetype=5&userdata=0&subcmd=1&subver=101&clip=0_0_0_0&filesize=%v",
c.getSKey(), c.Uin, groupCode, len(img))
req, _ := http.NewRequest("POST", url, bytes.NewReader(img))
req.Header["User-Agent"] = []string{"Dalvik/2.1.0 (Linux; U; Android 7.1.2; PCRT00 Build/N2G48H)"}
req.Header["Content-Type"] = []string{"multipart/form-data;boundary=****"}
rsp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "failed to upload group head portrait")
}
rsp.Body.Close()
return nil
}
func (g *GroupInfo) UpdateName(newName string) { func (g *GroupInfo) UpdateName(newName string) {
if g.AdministratorOrOwner() && newName != "" && strings.Count(newName, "") <= 20 { if g.AdministratorOrOwner() && newName != "" && strings.Count(newName, "") <= 20 {
g.client.updateGroupName(g.Code, newName) g.client.updateGroupName(g.Code, newName)

View File

@ -13,6 +13,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/Mrs4s/MiraiGo/client/internal/highway"
"github.com/Mrs4s/MiraiGo/client/pb/longmsg" "github.com/Mrs4s/MiraiGo/client/pb/longmsg"
"github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/client/pb/multimsg" "github.com/Mrs4s/MiraiGo/client/pb/multimsg"
@ -169,7 +170,13 @@ func (c *QQClient) uploadGroupLongMessage(groupCode int64, m *message.ForwardMes
return nil, errors.Errorf("upload long message error: %v", err) return nil, errors.Errorf("upload long message error: %v", err)
} }
for i, ip := range rsp.Uint32UpIp { for i, ip := range rsp.Uint32UpIp {
err := c.highwayUpload(uint32(ip), int(rsp.Uint32UpPort[i]), rsp.MsgSig, body, 27) addr := highway.Addr{IP: uint32(ip), Port: int(rsp.Uint32UpPort[i])}
input := highway.Input{
CommandID: 27,
Key: rsp.MsgSig,
Body: bytes.NewReader(body),
}
err := c.highwaySession.Upload(addr, input)
if err != nil { if err != nil {
c.Error("highway upload long message error: %v", err) c.Error("highway upload long message error: %v", err)
continue continue
@ -191,7 +198,13 @@ func (c *QQClient) UploadGroupForwardMessage(groupCode int64, m *message.Forward
return nil return nil
} }
for i, ip := range rsp.Uint32UpIp { for i, ip := range rsp.Uint32UpIp {
err := c.highwayUpload(uint32(ip), int(rsp.Uint32UpPort[i]), rsp.MsgSig, body, 27) addr := highway.Addr{IP: uint32(ip), Port: int(rsp.Uint32UpPort[i])}
input := highway.Input{
CommandID: 27,
Key: rsp.MsgSig,
Body: bytes.NewReader(body),
}
err := c.highwaySession.Upload(addr, input)
if err != nil { if err != nil {
continue continue
} }

View File

@ -793,7 +793,6 @@ func decodeGuildPushFirstView(c *QQClient, _ *incomingPacketInfo, payload []byte
} }
} }
if len(firstViewMsg.ChannelMsgs) > 0 { // sync msg if len(firstViewMsg.ChannelMsgs) > 0 { // sync msg
} }
return nil, nil return nil, nil
} }

View File

@ -17,9 +17,7 @@ func init() {
decoders["MsgPush.PushGroupProMsg"] = decodeGuildEventFlowPacket decoders["MsgPush.PushGroupProMsg"] = decodeGuildEventFlowPacket
} }
var ( var updateChanLock sync.Mutex
updateChanLock sync.Mutex
)
type tipsPushInfo struct { type tipsPushInfo struct {
TinyId uint64 TinyId uint64
@ -63,7 +61,6 @@ func decodeGuildEventFlowPacket(c *QQClient, _ *incomingPacketInfo, payload []by
} }
if m.Head.ContentHead.GetSubType() == 2 { // todo: tips? if m.Head.ContentHead.GetSubType() == 2 { // todo: tips?
if common == nil { // empty tips if common == nil { // empty tips
} }
tipsInfo := &tipsPushInfo{ tipsInfo := &tipsPushInfo{
TinyId: m.Head.RoutingHead.GetFromTinyid(), TinyId: m.Head.RoutingHead.GetFromTinyid(),

View File

@ -2,9 +2,7 @@ package client
import ( import (
"bytes" "bytes"
"crypto/md5"
"encoding/hex" "encoding/hex"
"fmt"
"image" "image"
"io" "io"
"math/rand" "math/rand"
@ -13,6 +11,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/internal/highway"
"github.com/Mrs4s/MiraiGo/client/pb/channel" "github.com/Mrs4s/MiraiGo/client/pb/channel"
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388" "github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
"github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/client/pb/msg"
@ -130,12 +129,18 @@ func (s *GuildService) UploadGuildImage(guildId, channelId uint64, img io.ReadSe
if body.IsExists { if body.IsExists {
goto ok goto ok
} }
if len(s.c.srvSsoAddrs) == 0 { if s.c.highwaySession.AddrLength() == 0 {
for i, addr := range body.UploadIp { for i, addr := range body.UploadIp {
s.c.srvSsoAddrs = append(s.c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr), body.UploadPort[i])) s.c.highwaySession.AppendAddr(addr, body.UploadPort[i])
} }
} }
if _, err = s.c.highwayUploadByBDH(img, length, 83, body.UploadKey, fh, binary.DynamicProtoMessage{11: guildId, 12: channelId}.Encode(), false); err == nil { if _, err = s.c.highwaySession.UploadBDH(highway.BdhInput{
CommandID: 83,
Body: img,
Ticket: body.UploadKey,
Ext: binary.DynamicProtoMessage{11: guildId, 12: channelId}.Encode(),
Encrypt: false,
}); err == nil {
goto ok goto ok
} }
return nil, errors.Wrap(err, "highway upload error") return nil, errors.Wrap(err, "highway upload error")
@ -383,14 +388,15 @@ func (c *QQClient) UploadGuildShortVideo(guildId, channelId uint64, video, thumb
req.BusinessType = 4601 req.BusinessType = 4601
req.ToUin = int64(channelId) req.ToUin = int64(channelId)
ext, _ := proto.Marshal(req) ext, _ := proto.Marshal(req)
var hwRsp []byte
multi := utils.MultiReadSeeker(thumb, video) multi := utils.MultiReadSeeker(thumb, video)
h := md5.New()
length, _ := io.Copy(h, multi)
fh := h.Sum(nil)
_, _ = multi.Seek(0, io.SeekStart)
hwRsp, err = c.highwayUploadByBDH(multi, length, 89, c.bigDataSession.SigSession, fh, ext, true) hwRsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
CommandID: 89,
Body: multi,
Ticket: c.highwaySession.SigSession,
Ext: ext,
Encrypt: true,
})
if err != nil { if err != nil {
return nil, errors.Wrap(err, "upload video file error") return nil, errors.Wrap(err, "upload video file error")
} }

View File

@ -1,499 +0,0 @@
package client
import (
"bytes"
"crypto/md5"
binary2 "encoding/binary"
"fmt"
"io"
"net"
"net/http"
"os"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/internal/proto"
"github.com/Mrs4s/MiraiGo/utils"
)
func (c *QQClient) highwayUpload(ip uint32, port int, updKey, data []byte, cmdID int32) error {
return c.highwayUploadStream(ip, port, updKey, bytes.NewReader(data), cmdID)
}
func (c *QQClient) highwayUploadStream(ip uint32, port int, updKey []byte, stream io.ReadSeeker, cmdId int32) error {
addr := net.TCPAddr{
IP: make([]byte, 4),
Port: port,
}
binary2.LittleEndian.PutUint32(addr.IP, ip)
h := md5.New()
length, _ := io.Copy(h, stream)
fh := h.Sum(nil)
const chunkSize = 8192 * 8
_, _ = stream.Seek(0, io.SeekStart)
conn, err := net.DialTCP("tcp", nil, &addr)
if err != nil {
return errors.Wrap(err, "connect error")
}
defer conn.Close()
offset := 0
reader := binary.NewNetworkReader(conn)
buf := binary.Get256KBytes()
chunk := *buf
defer binary.Put256KBytes(buf)
w := binary.SelectWriter()
defer binary.PutWriter(w)
for {
chunk = chunk[:chunkSize]
rl, err := io.ReadFull(stream, chunk)
if errors.Is(err, io.EOF) {
break
}
if errors.Is(err, io.ErrUnexpectedEOF) {
chunk = chunk[:rl]
}
ch := md5.Sum(chunk)
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: strconv.FormatInt(c.Uin, 10),
Command: "PicUp.DataUp",
Seq: c.nextGroupDataTransSeq(),
Appid: int32(c.version.AppId),
Dataflag: 4096,
CommandId: cmdId,
LocaleId: 2052,
},
MsgSeghead: &pb.SegHead{
Filesize: length,
Dataoffset: int64(offset),
Datalength: int32(rl),
Serviceticket: updKey,
Md5: ch[:],
FileMd5: fh,
},
ReqExtendinfo: EmptyBytes,
})
offset += rl
w.Reset()
w.WriteByte(40)
w.WriteUInt32(uint32(len(head)))
w.WriteUInt32(uint32(len(chunk)))
w.Write(head)
w.Write(chunk)
w.WriteByte(41)
_, err = conn.Write(w.Bytes())
if err != nil {
return errors.Wrap(err, "write conn error")
}
rspHead, _, err := highwayReadResponse(reader)
if err != nil {
return errors.Wrap(err, "highway upload error")
}
if rspHead.ErrorCode != 0 {
return errors.New("upload failed")
}
}
return nil
}
func (c *QQClient) highwayUploadByBDH(stream io.Reader, length int64, cmdId int32, ticket, sum, ext []byte, encrypt bool) ([]byte, error) {
if len(c.srvSsoAddrs) == 0 {
return nil, errors.New("srv addrs not found. maybe miss some packet?")
}
if encrypt {
if c.bigDataSession == nil || len(c.bigDataSession.SessionKey) == 0 {
return nil, errors.New("session key not found. maybe miss some packet?")
}
ext = binary.NewTeaCipher(c.bigDataSession.SessionKey).Encrypt(ext)
}
const chunkSize = 256 * 1024
conn, err := net.DialTimeout("tcp", c.srvSsoAddrs[0], time.Second*20)
if err != nil {
return nil, errors.Wrap(err, "connect error")
}
defer conn.Close()
offset := 0
reader := binary.NewNetworkReader(conn)
if err = c.highwaySendHeartbreak(conn); err != nil {
return nil, errors.Wrap(err, "echo error")
}
if _, _, err = highwayReadResponse(reader); err != nil {
return nil, errors.Wrap(err, "echo error")
}
var rspExt []byte
buf := binary.Get256KBytes()
chunk := *buf
defer binary.Put256KBytes(buf)
w := binary.SelectWriter()
defer binary.PutWriter(w)
for {
chunk = chunk[:chunkSize]
rl, err := io.ReadFull(stream, chunk)
if errors.Is(err, io.EOF) {
break
}
if errors.Is(err, io.ErrUnexpectedEOF) {
chunk = chunk[:rl]
}
ch := md5.Sum(chunk)
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: strconv.FormatInt(c.Uin, 10),
Command: "PicUp.DataUp",
Seq: c.nextGroupDataTransSeq(),
Appid: int32(c.version.AppId),
Dataflag: 4096,
CommandId: cmdId,
LocaleId: 2052,
},
MsgSeghead: &pb.SegHead{
Filesize: length,
Dataoffset: int64(offset),
Datalength: int32(rl),
Serviceticket: ticket,
Md5: ch[:],
FileMd5: sum,
},
ReqExtendinfo: ext,
})
offset += rl
w.Reset()
w.WriteByte(40)
w.WriteUInt32(uint32(len(head)))
w.WriteUInt32(uint32(len(chunk)))
w.Write(head)
w.Write(chunk)
w.WriteByte(41)
_, err = conn.Write(w.Bytes())
if err != nil {
return nil, errors.Wrap(err, "write conn error")
}
rspHead, _, err := highwayReadResponse(reader)
if err != nil {
return nil, errors.Wrap(err, "highway upload error")
}
if rspHead.ErrorCode != 0 {
return nil, errors.Errorf("upload failed: %d", rspHead.ErrorCode)
}
if rspHead.RspExtendinfo != nil {
rspExt = rspHead.RspExtendinfo
}
if rspHead.MsgSeghead != nil && rspHead.MsgSeghead.Serviceticket != nil {
ticket = rspHead.MsgSeghead.Serviceticket
}
}
return rspExt, nil
}
func (c *QQClient) highwayUploadFileMultiThreadingByBDH(path string, cmdId int32, threadCount int, ticket, ext []byte, encrypt bool) ([]byte, error) {
if len(c.srvSsoAddrs) == 0 {
return nil, errors.New("srv addrs not found. maybe miss some packet?")
}
if encrypt {
if c.bigDataSession == nil || len(c.bigDataSession.SessionKey) == 0 {
return nil, errors.New("session key not found. maybe miss some packet?")
}
ext = binary.NewTeaCipher(c.bigDataSession.SessionKey).Encrypt(ext)
}
stat, err := os.Stat(path)
if err != nil {
return nil, errors.Wrap(err, "get stat error")
}
file, err := os.OpenFile(path, os.O_RDONLY, 0o666)
if err != nil {
return nil, errors.Wrap(err, "open file error")
}
defer file.Close()
h := md5.New()
length, _ := io.Copy(h, file)
fh := h.Sum(nil)
_, _ = file.Seek(0, io.SeekStart)
if stat.Size() < 1024*1024*3 || threadCount < 2 {
return c.highwayUploadByBDH(file, length, cmdId, ticket, fh, ext, false)
}
type BlockMetaData struct {
Id int
BeginOffset int64
EndOffset int64
}
const blockSize int64 = 1024 * 512
var (
blocks []*BlockMetaData
rspExt []byte
BlockId = ^uint32(0) // -1
uploadedCount uint32
cond = sync.NewCond(&sync.Mutex{})
)
// Init Blocks
{
var temp int64 = 0
for temp+blockSize < stat.Size() {
blocks = append(blocks, &BlockMetaData{
Id: len(blocks),
BeginOffset: temp,
EndOffset: temp + blockSize,
})
temp += blockSize
}
blocks = append(blocks, &BlockMetaData{
Id: len(blocks),
BeginOffset: temp,
EndOffset: stat.Size(),
})
}
doUpload := func() error {
defer cond.Signal()
conn, err := net.DialTimeout("tcp", c.srvSsoAddrs[0], time.Second*20)
if err != nil {
return errors.Wrap(err, "connect error")
}
defer conn.Close()
chunk, _ := os.OpenFile(path, os.O_RDONLY, 0o666)
defer chunk.Close()
reader := binary.NewNetworkReader(conn)
if err = c.highwaySendHeartbreak(conn); err != nil {
return errors.Wrap(err, "echo error")
}
if _, _, err = highwayReadResponse(reader); err != nil {
return errors.Wrap(err, "echo error")
}
buffer := make([]byte, blockSize)
w := binary.SelectWriter()
w.Reset()
w.Grow(600 * 1024) // 复用,600k 不要放回池中
for {
nextId := atomic.AddUint32(&BlockId, 1)
if nextId >= uint32(len(blocks)) {
break
}
block := blocks[nextId]
if block.Id == len(blocks)-1 {
cond.L.Lock()
for atomic.LoadUint32(&uploadedCount) != uint32(len(blocks))-1 {
cond.Wait()
}
cond.L.Unlock()
}
buffer = buffer[:blockSize]
_, _ = chunk.Seek(block.BeginOffset, io.SeekStart)
ri, err := io.ReadFull(chunk, buffer)
if err != nil {
if err == io.EOF {
break
}
if err == io.ErrUnexpectedEOF {
buffer = buffer[:ri]
} else {
return err
}
}
ch := md5.Sum(buffer)
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: strconv.FormatInt(c.Uin, 10),
Command: "PicUp.DataUp",
Seq: c.nextGroupDataTransSeq(),
Appid: int32(c.version.AppId),
Dataflag: 4096,
CommandId: cmdId,
LocaleId: 2052,
},
MsgSeghead: &pb.SegHead{
Filesize: stat.Size(),
Dataoffset: block.BeginOffset,
Datalength: int32(ri),
Serviceticket: ticket,
Md5: ch[:],
FileMd5: fh,
},
ReqExtendinfo: ext,
})
w.Reset()
w.WriteByte(40)
w.WriteUInt32(uint32(len(head)))
w.WriteUInt32(uint32(len(buffer)))
w.Write(head)
w.Write(buffer)
w.WriteByte(41)
_, err = conn.Write(w.Bytes())
if err != nil {
return errors.Wrap(err, "write conn error")
}
rspHead, _, err := highwayReadResponse(reader)
if err != nil {
return errors.Wrap(err, "highway upload error")
}
if rspHead.ErrorCode != 0 {
return errors.Errorf("upload failed: %d", rspHead.ErrorCode)
}
if rspHead.RspExtendinfo != nil {
rspExt = rspHead.RspExtendinfo
}
atomic.AddUint32(&uploadedCount, 1)
}
return nil
}
group := errgroup.Group{}
for i := 0; i < threadCount; i++ {
group.Go(doUpload)
}
err = group.Wait()
return rspExt, err
}
func (c *QQClient) highwaySendHeartbreak(conn net.Conn) error {
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: strconv.FormatInt(c.Uin, 10),
Command: "PicUp.Echo",
Seq: c.nextGroupDataTransSeq(),
Appid: int32(c.version.AppId),
Dataflag: 4096,
CommandId: 0,
LocaleId: 2052,
},
})
w := binary.SelectWriter()
w.WriteByte(40)
w.WriteUInt32(uint32(len(head)))
w.WriteUInt32(0)
w.Write(head)
w.WriteByte(41)
_, err := conn.Write(w.Bytes())
binary.PutWriter(w)
return err
}
func highwayReadResponse(r *binary.NetworkReader) (*pb.RspDataHighwayHead, []byte, error) {
_, err := r.ReadByte()
if err != nil {
return nil, nil, errors.Wrap(err, "failed to read byte")
}
hl, _ := r.ReadInt32()
a2, _ := r.ReadInt32()
head, _ := r.ReadBytes(int(hl))
payload, _ := r.ReadBytes(int(a2))
_, _ = r.ReadByte()
rsp := new(pb.RspDataHighwayHead)
if err = proto.Unmarshal(head, rsp); err != nil {
return nil, nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
return rsp, payload, nil
}
func (c *QQClient) excitingUploadStream(stream io.ReadSeeker, cmdId int32, ticket, ext []byte) ([]byte, error) {
fileMd5, fileLength := utils.ComputeMd5AndLength(stream)
_, _ = stream.Seek(0, io.SeekStart)
url := fmt.Sprintf("http://%v/cgi-bin/httpconn?htcmd=0x6FF0087&uin=%v", c.srvSsoAddrs[0], c.Uin)
var (
rspExt []byte
offset int64 = 0
chunkSize = 524288
)
chunk := make([]byte, chunkSize)
w := binary.SelectWriter()
w.Reset()
w.Grow(600 * 1024) // 复用,600k 不要放回池中
for {
chunk = chunk[:chunkSize]
rl, err := io.ReadFull(stream, chunk)
if err == io.EOF {
break
}
if err == io.ErrUnexpectedEOF {
chunk = chunk[:rl]
}
ch := md5.Sum(chunk)
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: strconv.FormatInt(c.Uin, 10),
Command: "PicUp.DataUp",
Seq: c.nextGroupDataTransSeq(),
Appid: int32(c.version.AppId),
Dataflag: 0,
CommandId: cmdId,
LocaleId: 0,
},
MsgSeghead: &pb.SegHead{
Filesize: fileLength,
Dataoffset: offset,
Datalength: int32(rl),
Serviceticket: ticket,
Md5: ch[:],
FileMd5: fileMd5,
},
ReqExtendinfo: ext,
})
offset += int64(rl)
w.Reset()
w.WriteByte(40)
w.WriteUInt32(uint32(len(head)))
w.WriteUInt32(uint32(len(chunk)))
w.Write(head)
w.Write(chunk)
w.WriteByte(41)
req, _ := http.NewRequest("POST", url, bytes.NewReader(w.Bytes()))
req.Header.Set("Accept", "*/*")
req.Header.Set("Connection", "Keep-Alive")
req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)")
req.Header.Set("Pragma", "no-cache")
rsp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, errors.Wrap(err, "request error")
}
body, _ := io.ReadAll(rsp.Body)
_ = rsp.Body.Close()
r := binary.NewReader(body)
r.ReadByte()
hl := r.ReadInt32()
a2 := r.ReadInt32()
h := r.ReadBytes(int(hl))
r.ReadBytes(int(a2))
rspHead := new(pb.RspDataHighwayHead)
if err = proto.Unmarshal(h, rspHead); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if rspHead.ErrorCode != 0 {
return nil, errors.Errorf("upload failed: %d", rspHead.ErrorCode)
}
if rspHead.RspExtendinfo != nil {
rspExt = rspHead.RspExtendinfo
}
}
return rspExt, nil
}
func (c *QQClient) uploadGroupHeadPortrait(groupCode int64, img []byte) error {
url := fmt.Sprintf(
"http://htdata3.qq.com/cgi-bin/httpconn?htcmd=0x6ff0072&ver=5520&ukey=%v&range=0&uin=%v&seq=23&groupuin=%v&filetype=3&imagetype=5&userdata=0&subcmd=1&subver=101&clip=0_0_0_0&filesize=%v",
c.getSKey(),
c.Uin,
groupCode,
len(img),
)
req, _ := http.NewRequest("POST", url, bytes.NewReader(img))
req.Header["User-Agent"] = []string{"Dalvik/2.1.0 (Linux; U; Android 7.1.2; PCRT00 Build/N2G48H)"}
req.Header["Content-Type"] = []string{"multipart/form-data;boundary=****"}
rsp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "failed to upload group head portrait")
}
rsp.Body.Close()
return nil
}

View File

@ -3,7 +3,6 @@ package client
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt"
"image" "image"
_ "image/gif" _ "image/gif"
"io" "io"
@ -15,8 +14,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/internal/highway"
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388" "github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
"github.com/Mrs4s/MiraiGo/client/pb/highway" highway2 "github.com/Mrs4s/MiraiGo/client/pb/highway"
"github.com/Mrs4s/MiraiGo/client/pb/oidb" "github.com/Mrs4s/MiraiGo/client/pb/oidb"
"github.com/Mrs4s/MiraiGo/internal/packets" "github.com/Mrs4s/MiraiGo/internal/packets"
"github.com/Mrs4s/MiraiGo/internal/proto" "github.com/Mrs4s/MiraiGo/internal/proto"
@ -68,12 +68,18 @@ func (c *QQClient) UploadGroupImage(groupCode int64, img io.ReadSeeker) (*messag
if rsp.IsExists { if rsp.IsExists {
goto ok goto ok
} }
if len(c.srvSsoAddrs) == 0 { if c.highwaySession.AddrLength() == 0 {
for i, addr := range rsp.UploadIp { for i, addr := range rsp.UploadIp {
c.srvSsoAddrs = append(c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr), rsp.UploadPort[i])) c.highwaySession.AppendAddr(addr, rsp.UploadPort[i])
} }
} }
if _, err = c.highwayUploadByBDH(img, length, 2, rsp.UploadKey, fh, EmptyBytes, false); err == nil { if _, err = c.highwaySession.UploadBDH(highway.BdhInput{
CommandID: 2,
Body: img,
Ticket: rsp.UploadKey,
Ext: EmptyBytes,
Encrypt: false,
}); err == nil {
goto ok goto ok
} }
return nil, errors.Wrap(err, "upload failed") return nil, errors.Wrap(err, "upload failed")
@ -114,12 +120,19 @@ func (c *QQClient) UploadGroupImageByFile(groupCode int64, path string) (*messag
if rsp.IsExists { if rsp.IsExists {
goto ok goto ok
} }
if len(c.srvSsoAddrs) == 0 { if c.highwaySession.AddrLength() == 0 {
for i, addr := range rsp.UploadIp { for i, addr := range rsp.UploadIp {
c.srvSsoAddrs = append(c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr), rsp.UploadPort[i])) c.highwaySession.AppendAddr(addr, rsp.UploadPort[i])
} }
} }
if _, err = c.highwayUploadFileMultiThreadingByBDH(path, 2, 1, rsp.UploadKey, EmptyBytes, false); err == nil {
if _, err = c.highwaySession.UploadBDHMultiThread(highway.BdhInput{
CommandID: 2,
File: path,
Ticket: rsp.UploadKey,
Ext: EmptyBytes,
Encrypt: false,
}, 1); err == nil {
goto ok goto ok
} }
return nil, errors.Wrap(err, "upload failed") return nil, errors.Wrap(err, "upload failed")
@ -176,7 +189,7 @@ func (c *QQClient) ImageOcr(img interface{}) (*OcrResponse, error) {
case *message.GroupImageElement: case *message.GroupImageElement:
url = e.Url url = e.Url
if b, err := utils.HTTPGetReadCloser(e.Url, ""); err == nil { if b, err := utils.HTTPGetReadCloser(e.Url, ""); err == nil {
if url, err = c.uploadOcrImage(b, int64(e.Size), e.Md5); err != nil { if url, err = c.uploadOcrImage(b); err != nil {
url = e.Url url = e.Url
} }
_ = b.Close() _ = b.Close()
@ -285,18 +298,26 @@ func (c *QQClient) buildGroupImageDownloadPacket(fileId, groupCode int64, fileMd
return seq, packet return seq, packet
} }
func (c *QQClient) uploadOcrImage(img io.Reader, length int64, sum []byte) (string, error) { func (c *QQClient) uploadOcrImage(img io.Reader) (string, error) {
r := make([]byte, 16) r := make([]byte, 16)
rand.Read(r) rand.Read(r)
ext, _ := proto.Marshal(&highway.CommFileExtReq{ ext, _ := proto.Marshal(&highway2.CommFileExtReq{
ActionType: proto.Uint32(0), ActionType: proto.Uint32(0),
Uuid: binary.GenUUID(r), Uuid: binary.GenUUID(r),
}) })
rsp, err := c.highwayUploadByBDH(img, length, 76, c.bigDataSession.SigSession, sum, ext, false)
buf, _ := io.ReadAll(img)
rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
CommandID: 76,
Body: bytes.NewReader(buf),
Ticket: c.highwaySession.SigSession,
Ext: ext,
Encrypt: false,
})
if err != nil { if err != nil {
return "", errors.Wrap(err, "upload ocr image error") return "", errors.Wrap(err, "upload ocr image error")
} }
rspExt := highway.CommFileExtRsp{} rspExt := highway2.CommFileExtRsp{}
if err = proto.Unmarshal(rsp, &rspExt); err != nil { if err = proto.Unmarshal(rsp, &rspExt); err != nil {
return "", errors.Wrap(err, "error unmarshal highway resp") return "", errors.Wrap(err, "error unmarshal highway resp")
} }

View File

@ -0,0 +1,31 @@
package highway
import (
binary2 "encoding/binary"
"fmt"
"net"
"github.com/Mrs4s/MiraiGo/binary"
)
type Addr struct {
IP uint32
Port int
}
func (a Addr) asTcpAddr() *net.TCPAddr {
addr := &net.TCPAddr{
IP: make([]byte, 4),
Port: a.Port,
}
binary2.LittleEndian.PutUint32(addr.IP, a.IP)
return addr
}
func (a Addr) AsNetIP() net.IP {
return net.IPv4(byte(a.IP>>24), byte(a.IP>>16), byte(a.IP>>8), byte(a.IP))
}
func (a Addr) String() string {
return fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(a.IP), a.Port)
}

View File

@ -0,0 +1,275 @@
package highway
import (
"crypto/md5"
"io"
"net"
"os"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/internal/proto"
"github.com/Mrs4s/MiraiGo/utils"
)
type BdhInput struct {
CommandID int32
File string // upload multi-thread required
Body io.ReadSeeker
Ticket []byte
Ext []byte
Encrypt bool
}
func (bdh *BdhInput) encrypt(key []byte) error {
if bdh.Encrypt {
if len(key) == 0 {
return errors.New("session key not found. maybe miss some packet?")
}
bdh.Ext = binary.NewTeaCipher(key).Encrypt(bdh.Ext)
}
return nil
}
func (s *Session) UploadBDH(input BdhInput) ([]byte, error) {
if len(s.SsoAddr) == 0 {
return nil, errors.New("srv addrs not found. maybe miss some packet?")
}
addr := s.SsoAddr[0].String()
sum, length := utils.ComputeMd5AndLength(input.Body)
_, _ = input.Body.Seek(0, io.SeekStart)
if err := input.encrypt(s.SessionKey); err != nil {
return nil, errors.Wrap(err, "encrypt error")
}
conn, err := net.DialTimeout("tcp", addr, time.Second*20)
if err != nil {
return nil, errors.Wrap(err, "connect error")
}
defer conn.Close()
offset := 0
reader := binary.NewNetworkReader(conn)
if err = s.sendEcho(conn); err != nil {
return nil, err
}
var rspExt []byte
const chunkSize = 256 * 1024
chunk := make([]byte, chunkSize)
w := binary.SelectWriter()
defer binary.PutWriter(w)
for {
chunk = chunk[:chunkSize]
rl, err := io.ReadFull(input.Body, chunk)
if errors.Is(err, io.EOF) {
break
}
if errors.Is(err, io.ErrUnexpectedEOF) {
chunk = chunk[:rl]
}
ch := md5.Sum(chunk)
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: s.uin,
Command: "PicUp.DataUp",
Seq: s.nextSeq(),
Appid: s.appID,
Dataflag: 4096,
CommandId: input.CommandID,
LocaleId: 2052,
},
MsgSeghead: &pb.SegHead{
Filesize: length,
Dataoffset: int64(offset),
Datalength: int32(rl),
Serviceticket: input.Ticket,
Md5: ch[:],
FileMd5: sum,
},
ReqExtendinfo: input.Ext,
})
offset += rl
w.Reset()
writeHeadBody(w, head, chunk)
_, err = conn.Write(w.Bytes())
if err != nil {
return nil, errors.Wrap(err, "write conn error")
}
rspHead, _, err := readResponse(reader)
if err != nil {
return nil, errors.Wrap(err, "highway upload error")
}
if rspHead.ErrorCode != 0 {
return nil, errors.Errorf("upload failed: %d", rspHead.ErrorCode)
}
if rspHead.RspExtendinfo != nil {
rspExt = rspHead.RspExtendinfo
}
if rspHead.MsgSeghead != nil && rspHead.MsgSeghead.Serviceticket != nil {
input.Ticket = rspHead.MsgSeghead.Serviceticket
}
}
return rspExt, nil
}
func (s *Session) UploadBDHMultiThread(input BdhInput, threadCount int) ([]byte, error) {
if len(s.SsoAddr) == 0 {
return nil, errors.New("srv addrs not found. maybe miss some packet?")
}
addr := s.SsoAddr[0].String()
stat, err := os.Stat(input.File)
if err != nil {
return nil, errors.Wrap(err, "get stat error")
}
file, err := os.OpenFile(input.File, os.O_RDONLY, 0o666)
if err != nil {
return nil, errors.Wrap(err, "open file error")
}
sum, length := utils.ComputeMd5AndLength(file)
_, _ = file.Seek(0, io.SeekStart)
if err := input.encrypt(s.SessionKey); err != nil {
return nil, errors.Wrap(err, "encrypt error")
}
// for small file and small thread count,
// use UploadBDH instead of UploadBDHMultiThread
if length < 1024*1024*3 || threadCount < 2 {
input.Body = file
return s.UploadBDH(input)
}
type BlockMetaData struct {
Id int
BeginOffset int64
EndOffset int64
}
const blockSize int64 = 1024 * 512
var (
blocks []*BlockMetaData
rspExt []byte
BlockId = ^uint32(0) // -1
uploadedCount uint32
cond = sync.NewCond(&sync.Mutex{})
)
// Init Blocks
{
var temp int64 = 0
for temp+blockSize < stat.Size() {
blocks = append(blocks, &BlockMetaData{
Id: len(blocks),
BeginOffset: temp,
EndOffset: temp + blockSize,
})
temp += blockSize
}
blocks = append(blocks, &BlockMetaData{
Id: len(blocks),
BeginOffset: temp,
EndOffset: stat.Size(),
})
}
doUpload := func() error {
// send signal complete uploading
defer cond.Signal()
conn, err := net.DialTimeout("tcp", addr, time.Second*20)
if err != nil {
return errors.Wrap(err, "connect error")
}
defer conn.Close()
chunk, _ := os.OpenFile(input.File, os.O_RDONLY, 0o666)
defer chunk.Close()
reader := binary.NewNetworkReader(conn)
if err = s.sendEcho(conn); err != nil {
return err
}
buffer := make([]byte, blockSize)
w := binary.SelectWriter()
w.Reset()
w.Grow(600 * 1024) // 复用,600k 不要放回池中
for {
nextId := atomic.AddUint32(&BlockId, 1)
if nextId >= uint32(len(blocks)) {
break
}
block := blocks[nextId]
if block.Id == len(blocks)-1 {
cond.L.Lock()
for atomic.LoadUint32(&uploadedCount) != uint32(len(blocks))-1 {
cond.Wait()
}
cond.L.Unlock()
}
buffer = buffer[:blockSize]
_, _ = chunk.Seek(block.BeginOffset, io.SeekStart)
ri, err := io.ReadFull(chunk, buffer)
if err != nil {
if err == io.EOF {
break
}
if err == io.ErrUnexpectedEOF {
buffer = buffer[:ri]
} else {
return err
}
}
ch := md5.Sum(buffer)
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: s.uin,
Command: "PicUp.DataUp",
Seq: s.nextSeq(),
Appid: s.appID,
Dataflag: 4096,
CommandId: input.CommandID,
LocaleId: 2052,
},
MsgSeghead: &pb.SegHead{
Filesize: stat.Size(),
Dataoffset: block.BeginOffset,
Datalength: int32(ri),
Serviceticket: input.Ticket,
Md5: ch[:],
FileMd5: sum,
},
ReqExtendinfo: input.Ext,
})
w.Reset()
writeHeadBody(w, head, buffer)
_, err = conn.Write(w.Bytes())
if err != nil {
return errors.Wrap(err, "write conn error")
}
rspHead, _, err := readResponse(reader)
if err != nil {
return errors.Wrap(err, "highway upload error")
}
if rspHead.ErrorCode != 0 {
return errors.Errorf("upload failed: %d", rspHead.ErrorCode)
}
if rspHead.RspExtendinfo != nil {
rspExt = rspHead.RspExtendinfo
}
atomic.AddUint32(&uploadedCount, 1)
}
return nil
}
group := errgroup.Group{}
for i := 0; i < threadCount; i++ {
group.Go(doUpload)
}
err = group.Wait()
return rspExt, err
}

View File

@ -0,0 +1,265 @@
package highway
import (
"bytes"
"crypto/md5"
"fmt"
"io"
"net"
"net/http"
"strconv"
"sync/atomic"
"github.com/pkg/errors"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/internal/proto"
"github.com/Mrs4s/MiraiGo/utils"
)
type Session struct {
SigSession []byte
SessionKey []byte
SsoAddr []Addr
seq int32
appID int32
uin string
}
func NewSession(appID int32, uin int64) *Session {
return &Session{
appID: appID,
uin: strconv.FormatInt(uin, 10),
}
}
func (s *Session) AddrLength() int {
return len(s.SsoAddr)
}
func (s *Session) AppendAddr(ip, port uint32) {
addr := Addr{
IP: ip,
Port: int(port),
}
s.SsoAddr = append(s.SsoAddr, addr)
}
type Input struct {
CommandID int32
Key []byte
Body io.ReadSeeker
}
func (s *Session) Upload(addr Addr, input Input) error {
fh, length := utils.ComputeMd5AndLength(input.Body)
_, _ = input.Body.Seek(0, io.SeekStart)
conn, err := net.DialTCP("tcp", nil, addr.asTcpAddr())
if err != nil {
return errors.Wrap(err, "connect error")
}
defer conn.Close()
const chunkSize = 8192 * 8
chunk := make([]byte, chunkSize)
offset := 0
reader := binary.NewNetworkReader(conn)
w := binary.SelectWriter()
defer binary.PutWriter(w)
for {
chunk = chunk[:chunkSize]
rl, err := io.ReadFull(input.Body, chunk)
if errors.Is(err, io.EOF) {
break
}
if errors.Is(err, io.ErrUnexpectedEOF) {
chunk = chunk[:rl]
}
ch := md5.Sum(chunk)
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: s.uin,
Command: "PicUp.DataUp",
Seq: s.nextSeq(),
Appid: s.appID,
Dataflag: 4096,
CommandId: input.CommandID,
LocaleId: 2052,
},
MsgSeghead: &pb.SegHead{
Filesize: length,
Dataoffset: int64(offset),
Datalength: int32(rl),
Serviceticket: input.Key,
Md5: ch[:],
FileMd5: fh,
},
ReqExtendinfo: []byte{},
})
offset += rl
w.Reset()
writeHeadBody(w, head, chunk)
_, err = conn.Write(w.Bytes())
if err != nil {
return errors.Wrap(err, "write conn error")
}
rspHead, _, err := readResponse(reader)
if err != nil {
return errors.Wrap(err, "highway upload error")
}
if rspHead.ErrorCode != 0 {
return errors.New("upload failed")
}
}
return nil
}
type ExcitingInput struct {
CommandID int32
Body io.ReadSeeker
Ticket []byte
Ext []byte
}
func (s *Session) UploadExciting(input ExcitingInput) ([]byte, error) {
fileMd5, fileLength := utils.ComputeMd5AndLength(input.Body)
_, _ = input.Body.Seek(0, io.SeekStart)
addr := s.SsoAddr[0]
url := fmt.Sprintf("http://%v/cgi-bin/httpconn?htcmd=0x6FF0087&uin=%v", addr, s.uin)
var (
rspExt []byte
offset int64 = 0
chunkSize = 524288
)
chunk := make([]byte, chunkSize)
w := binary.SelectWriter()
w.Reset()
w.Grow(600 * 1024) // 复用,600k 不要放回池中
for {
chunk = chunk[:chunkSize]
rl, err := io.ReadFull(input.Body, chunk)
if err == io.EOF {
break
}
if err == io.ErrUnexpectedEOF {
chunk = chunk[:rl]
}
ch := md5.Sum(chunk)
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: s.uin,
Command: "PicUp.DataUp",
Seq: s.nextSeq(),
Appid: s.appID,
Dataflag: 0,
CommandId: input.CommandID,
LocaleId: 0,
},
MsgSeghead: &pb.SegHead{
Filesize: fileLength,
Dataoffset: offset,
Datalength: int32(rl),
Serviceticket: input.Ticket,
Md5: ch[:],
FileMd5: fileMd5,
},
ReqExtendinfo: input.Ext,
})
offset += int64(rl)
w.Reset()
writeHeadBody(w, head, chunk)
req, _ := http.NewRequest("POST", url, bytes.NewReader(w.Bytes()))
req.Header.Set("Accept", "*/*")
req.Header.Set("Connection", "Keep-Alive")
req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)")
req.Header.Set("Pragma", "no-cache")
rsp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, errors.Wrap(err, "request error")
}
body, _ := io.ReadAll(rsp.Body)
_ = rsp.Body.Close()
r := binary.NewReader(body)
r.ReadByte()
hl := r.ReadInt32()
a2 := r.ReadInt32()
h := r.ReadBytes(int(hl))
r.ReadBytes(int(a2))
rspHead := new(pb.RspDataHighwayHead)
if err = proto.Unmarshal(h, rspHead); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if rspHead.ErrorCode != 0 {
return nil, errors.Errorf("upload failed: %d", rspHead.ErrorCode)
}
if rspHead.RspExtendinfo != nil {
rspExt = rspHead.RspExtendinfo
}
}
return rspExt, nil
}
func (s *Session) nextSeq() int32 {
return atomic.AddInt32(&s.seq, 2)
}
func (s *Session) sendHeartbreak(conn net.Conn) error {
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
MsgBasehead: &pb.DataHighwayHead{
Version: 1,
Uin: s.uin,
Command: "PicUp.Echo",
Seq: s.nextSeq(),
Appid: s.appID,
Dataflag: 4096,
CommandId: 0,
LocaleId: 2052,
},
})
w := binary.SelectWriter()
writeHeadBody(w, head, nil)
_, err := conn.Write(w.Bytes())
binary.PutWriter(w)
return err
}
func (s *Session) sendEcho(conn net.Conn) error {
err := s.sendHeartbreak(conn)
if err != nil {
return errors.Wrap(err, "echo error")
}
if _, _, err = readResponse(binary.NewNetworkReader(conn)); err != nil {
return errors.Wrap(err, "echo error")
}
return nil
}
func writeHeadBody(w *binary.Writer, head []byte, body []byte) {
w.WriteByte(40)
w.WriteUInt32(uint32(len(head)))
w.WriteUInt32(uint32(len(body)))
w.Write(head)
w.Write(body)
w.WriteByte(41)
}
func readResponse(r *binary.NetworkReader) (*pb.RspDataHighwayHead, []byte, error) {
_, err := r.ReadByte()
if err != nil {
return nil, nil, errors.Wrap(err, "failed to read byte")
}
hl, _ := r.ReadInt32()
a2, _ := r.ReadInt32()
head, _ := r.ReadBytes(int(hl))
payload, _ := r.ReadBytes(int(a2))
_, _ = r.ReadByte()
rsp := new(pb.RspDataHighwayHead)
if err = proto.Unmarshal(head, rsp); err != nil {
return nil, nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
return rsp, payload, nil
}

View File

@ -3,7 +3,6 @@ package client
import ( import (
"net" "net"
"runtime/debug" "runtime/debug"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -40,7 +39,8 @@ func (c *QQClient) ConnectionQualityTest() *ConnectionQualityInfo {
r := &ConnectionQualityInfo{} r := &ConnectionQualityInfo{}
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(2) wg.Add(2)
go func(w *sync.WaitGroup) { go func() {
defer wg.Done()
var err error var err error
if r.ChatServerLatency, err = qualityTest(c.servers[c.currServerIndex].String()); err != nil { if r.ChatServerLatency, err = qualityTest(c.servers[c.currServerIndex].String()); err != nil {
@ -57,24 +57,22 @@ func (c *QQClient) ConnectionQualityTest() *ConnectionQualityInfo {
c.Error("resolve long message server error: %v", err) c.Error("resolve long message server error: %v", err)
r.LongMessageServerLatency = 9999 r.LongMessageServerLatency = 9999
} }
if len(c.srvSsoAddrs) > 0 { if c.highwaySession.AddrLength() > 0 {
if r.SrvServerLatency, err = qualityTest(c.srvSsoAddrs[0]); err != nil { if r.SrvServerLatency, err = qualityTest(c.highwaySession.SsoAddr[0].String()); err != nil {
c.Error("test srv server latency error: %v", err) c.Error("test srv server latency error: %v", err)
r.SrvServerLatency = 9999 r.SrvServerLatency = 9999
} }
} }
}()
w.Done() go func() {
}(&wg) defer wg.Done()
go func(w *sync.WaitGroup) {
res := utils.RunICMPPingLoop(&net.IPAddr{IP: c.servers[c.currServerIndex].IP}, 10) res := utils.RunICMPPingLoop(&net.IPAddr{IP: c.servers[c.currServerIndex].IP}, 10)
r.ChatServerPacketLoss = res.PacketsLoss r.ChatServerPacketLoss = res.PacketsLoss
if len(c.srvSsoAddrs) > 0 { if c.highwaySession.AddrLength() > 0 {
res = utils.RunICMPPingLoop(&net.IPAddr{IP: net.ParseIP(strings.Split(c.srvSsoAddrs[0], ":")[0])}, 10) res = utils.RunICMPPingLoop(&net.IPAddr{IP: c.highwaySession.SsoAddr[0].AsNetIP()}, 10)
r.SrvServerPacketLoss = res.PacketsLoss r.SrvServerPacketLoss = res.PacketsLoss
} }
w.Done() }()
}(&wg)
start := time.Now() start := time.Now()
if _, err := utils.HttpGetBytes("https://ssl.htdata.qq.com", ""); err == nil { if _, err := utils.HttpGetBytes("https://ssl.htdata.qq.com", ""); err == nil {
r.LongMessageServerResponseLatency = time.Now().Sub(start).Milliseconds() r.LongMessageServerResponseLatency = time.Now().Sub(start).Milliseconds()

View File

@ -9,6 +9,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/internal/highway"
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x346" "github.com/Mrs4s/MiraiGo/client/pb/cmd0x346"
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388" "github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
"github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/client/pb/msg"
@ -28,17 +29,21 @@ var pttWaiter = utils.NewUploadWaiter()
// UploadGroupPtt 将语音数据使用群语音通道上传到服务器, 返回 message.GroupVoiceElement 可直接发送 // UploadGroupPtt 将语音数据使用群语音通道上传到服务器, 返回 message.GroupVoiceElement 可直接发送
func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*message.GroupVoiceElement, error) { func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*message.GroupVoiceElement, error) {
h := md5.New() fh, length := utils.ComputeMd5AndLength(voice)
length, _ := io.Copy(h, voice)
fh := h.Sum(nil)
_, _ = voice.Seek(0, io.SeekStart) _, _ = voice.Seek(0, io.SeekStart)
key := hex.EncodeToString(fh) key := string(fh)
pttWaiter.Wait(key) pttWaiter.Wait(key)
defer pttWaiter.Done(key) defer pttWaiter.Done(key)
ext := c.buildGroupPttStoreBDHExt(groupCode, fh[:], int32(length), 0, int32(length)) ext := c.buildGroupPttStoreBDHExt(groupCode, fh[:], int32(length), 0, int32(length))
rsp, err := c.highwayUploadByBDH(voice, length, 29, c.bigDataSession.SigSession, fh, ext, false) rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
CommandID: 29,
Body: voice,
Ticket: c.highwaySession.SigSession,
Ext: ext,
Encrypt: false,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -78,7 +83,13 @@ func (c *QQClient) UploadPrivatePtt(target int64, voice io.ReadSeeker) (*message
defer pttWaiter.Done(key) defer pttWaiter.Done(key)
ext := c.buildC2CPttStoreBDHExt(target, fh[:], int32(length), int32(length)) ext := c.buildC2CPttStoreBDHExt(target, fh[:], int32(length), int32(length))
rsp, err := c.highwayUploadByBDH(voice, length, 26, c.bigDataSession.SigSession, fh, ext, false) rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
CommandID: 26,
Body: voice,
Ticket: c.highwaySession.SigSession,
Ext: ext,
Encrypt: false,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -135,12 +146,17 @@ func (c *QQClient) UploadGroupShortVideo(groupCode int64, video, thumb io.ReadSe
}, nil }, nil
} }
ext, _ := proto.Marshal(c.buildPttGroupShortVideoProto(videoHash, thumbHash, groupCode, videoLen, thumbLen, 1).PttShortVideoUploadReq) ext, _ := proto.Marshal(c.buildPttGroupShortVideoProto(videoHash, thumbHash, groupCode, videoLen, thumbLen, 1).PttShortVideoUploadReq)
var hwRsp []byte var hwRsp []byte
multi := utils.MultiReadSeeker(thumb, video) multi := utils.MultiReadSeeker(thumb, video)
h := md5.New() input := highway.BdhInput{
length, _ := io.Copy(h, multi) CommandID: 25,
fh := h.Sum(nil) File: cache,
_, _ = multi.Seek(0, io.SeekStart) Body: multi,
Ticket: c.highwaySession.SigSession,
Ext: ext,
Encrypt: true,
}
if cache != "" { if cache != "" {
var file *os.File var file *os.File
file, err = os.OpenFile(cache, os.O_WRONLY|os.O_CREATE, 0o666) file, err = os.OpenFile(cache, os.O_WRONLY|os.O_CREATE, 0o666)
@ -149,14 +165,14 @@ func (c *QQClient) UploadGroupShortVideo(groupCode int64, video, thumb io.ReadSe
return err return err
} }
if err != nil || cp() != nil { if err != nil || cp() != nil {
hwRsp, err = c.highwayUploadByBDH(multi, length, 25, c.bigDataSession.SigSession, fh, ext, true) hwRsp, err = c.highwaySession.UploadBDH(input)
} else { } else {
_ = file.Close() _ = file.Close()
hwRsp, err = c.highwayUploadFileMultiThreadingByBDH(cache, 25, 8, c.bigDataSession.SigSession, ext, true) hwRsp, err = c.highwaySession.UploadBDHMultiThread(input, 8)
_ = os.Remove(cache) _ = os.Remove(cache)
} }
} else { } else {
hwRsp, err = c.highwayUploadByBDH(multi, length, 25, c.bigDataSession.SigSession, fh, ext, true) hwRsp, err = c.highwaySession.UploadBDH(input)
} }
if err != nil { if err != nil {
return nil, errors.Wrap(err, "upload video file error") return nil, errors.Wrap(err, "upload video file error")

View File

@ -80,7 +80,7 @@ func main() {
fmt.Printf("%s\n", buf.Bytes()) fmt.Printf("%s\n", buf.Bytes())
panic(err) panic(err)
} }
_ = os.WriteFile(*output, formated, 0644) _ = os.WriteFile(*output, formated, 0o644)
} }
func (g Generator) Generate(w io.Writer) { func (g Generator) Generate(w io.Writer) {

View File

@ -58,9 +58,7 @@ type (
content map[string]interface{} content map[string]interface{}
) )
var ( var globalBlockId int64 = 0
globalBlockId int64 = 0
)
func genBlockId() string { func genBlockId() string {
id := atomic.AddInt64(&globalBlockId, 1) id := atomic.AddInt64(&globalBlockId, 1)