github.com/LagrangeDev/LagrangeGo@v0.0.0-20240512064304-ad4a85e10cb4/client/highway.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "math/rand" 9 "net/http" 10 "net/url" 11 "strconv" 12 13 hw "github.com/LagrangeDev/LagrangeGo/client/internal/highway" 14 highway2 "github.com/LagrangeDev/LagrangeGo/client/packets/highway" 15 "github.com/LagrangeDev/LagrangeGo/client/packets/pb/service/highway" 16 "github.com/LagrangeDev/LagrangeGo/utils/binary" 17 "github.com/LagrangeDev/LagrangeGo/utils/crypto" 18 "github.com/RomiChan/protobuf/proto" 19 ) 20 21 func (c *QQClient) ensureHighwayServers() error { 22 if c.highwaySession.SsoAddr == nil || c.highwaySession.SigSession == nil || c.highwaySession.SessionKey == nil { 23 packet, err := highway2.BuildHighWayUrlReq(c.transport.Sig.Tgt) 24 if err != nil { 25 return err 26 } 27 payload, err := c.sendUniPacketAndWait("HttpConn.0x6ff_501", packet) 28 if err != nil { 29 return fmt.Errorf("get highway server: %v", err) 30 } 31 resp, err := highway2.ParseHighWayUrlReq(payload) 32 if err != nil { 33 return fmt.Errorf("parse highway server: %v", err) 34 } 35 c.highwaySession.SigSession = resp.HttpConn.SigSession 36 c.highwaySession.SessionKey = resp.HttpConn.SessionKey 37 for _, info := range resp.HttpConn.ServerInfos { 38 if info.ServiceType != 1 { 39 continue 40 } 41 for _, addr := range info.ServerAddrs { 42 c.debugln("add highway server", binary.UInt32ToIPV4Address(addr.IP), "port", addr.Port) 43 c.highwaySession.AppendAddr(addr.IP, addr.Port) 44 } 45 } 46 } 47 if c.highwaySession.SsoAddr == nil || c.highwaySession.SigSession == nil || c.highwaySession.SessionKey == nil { 48 return errors.New("empty highway servers") 49 } 50 return nil 51 } 52 53 func (c *QQClient) highwayUpload(commonId int, r io.Reader, fileSize uint64, md5 []byte, extendInfo []byte) error { 54 err := c.ensureHighwayServers() 55 if err != nil { 56 return err 57 } 58 trans := &hw.Transaction{ 59 CommandID: uint32(commonId), 60 Body: r, 61 Sum: md5, 62 Size: fileSize, 63 Ticket: c.highwaySession.SigSession, 64 LoginSig: c.transport.Sig.Tgt, 65 Ext: extendInfo, 66 } 67 _, err = c.highwaySession.Upload(trans) 68 if err == nil { 69 return nil 70 } 71 // fallback to http upload 72 servers := c.highwaySession.SsoAddr 73 saddr := servers[rand.Intn(len(servers))] 74 server := fmt.Sprintf( 75 "http://%s:%d/cgi-bin/httpconn?htcmd=0x6FF0087&uin=%d", 76 binary.UInt32ToIPV4Address(saddr.IP), saddr.Port, c.transport.Sig.Uin, 77 ) 78 buffer := make([]byte, hw.BlockSize) 79 for offset := uint64(0); offset < fileSize; offset += hw.BlockSize { 80 if hw.BlockSize > fileSize-offset { 81 buffer = buffer[:fileSize-offset] 82 } 83 _, err := io.ReadFull(r, buffer) 84 if err != nil { 85 return err 86 } 87 err = c.highwayUploadBlock(trans, server, offset, crypto.MD5Digest(buffer), buffer) 88 if err != nil { 89 return err 90 } 91 } 92 return nil 93 } 94 95 func (c *QQClient) highwayUploadBlock(trans *hw.Transaction, server string, offset uint64, blkmd5 []byte, blk []byte) error { 96 blksz := uint64(len(blk)) 97 isEnd := offset+blksz == trans.Size 98 payload, err := sendHighwayPacket( 99 trans.Build(&c.highwaySession, offset, uint32(blksz), blkmd5), blk, server, isEnd, 100 ) 101 if err != nil { 102 return fmt.Errorf("send highway packet: %v", err) 103 } 104 defer payload.Close() 105 resphead, respbody, err := parseHighwayPacket(payload) 106 if err != nil { 107 return fmt.Errorf("parse highway packet: %v", err) 108 } 109 c.debug("Highway Block Result: %d | %d | %x | %v", 110 resphead.ErrorCode, resphead.MsgSegHead.RetCode.Unwrap(), resphead.BytesRspExtendInfo, respbody) 111 if resphead.ErrorCode != 0 { 112 return errors.New("highway error code: " + strconv.Itoa(int(resphead.ErrorCode))) 113 } 114 return nil 115 } 116 117 func parseHighwayPacket(data io.Reader) (head *highway.RespDataHighwayHead, body *binary.Reader, err error) { 118 reader := binary.ParseReader(data) 119 if reader.ReadBytesNoCopy(1)[0] != 0x28 { 120 return nil, nil, errors.New("invalid highway packet") 121 } 122 headlength := reader.ReadU32() 123 _ = reader.ReadU32() // body len 124 head = &highway.RespDataHighwayHead{} 125 headraw := reader.ReadBytesNoCopy(int(int64(headlength))) 126 err = proto.Unmarshal(headraw, head) 127 if err != nil { 128 return nil, nil, err 129 } 130 if reader.ReadBytesNoCopy(1)[0] != 0x29 { 131 return nil, nil, errors.New("invalid highway head") 132 } 133 return head, reader, nil 134 } 135 136 func sendHighwayPacket(packet *highway.ReqDataHighwayHead, block []byte, serverURL string, end bool) (io.ReadCloser, error) { 137 marshal, err := proto.Marshal(packet) 138 if err != nil { 139 return nil, err 140 } 141 buf := hw.Frame(marshal, block) 142 data, err := io.ReadAll(&buf) 143 if err != nil { 144 return nil, err 145 } 146 return postHighwayContent(bytes.NewReader(data), serverURL, end) 147 /* 148 return postHighwayContent( 149 binary.NewBuilder(nil). 150 WriteBytes([]byte{0x28}). 151 WriteU32(uint32(len(marshal))). 152 WriteU32(uint32(len(block))). 153 WriteBytes(marshal). 154 WriteBytes(block). 155 WriteBytes([]byte{0x29}).ToReader(), serverURL, end) 156 */ 157 } 158 159 func postHighwayContent(content io.Reader, serverURL string, end bool) (io.ReadCloser, error) { 160 // Parse server URL 161 server, err := url.Parse(serverURL) 162 if err != nil { 163 return nil, err 164 } 165 166 req, err := http.NewRequest("POST", server.String(), content) 167 if err != nil { 168 return nil, err 169 } 170 171 // Set headers 172 if end { 173 req.Header.Set("Connection", "close") 174 } else { 175 req.Header.Set("Connection", "keep-alive") 176 } 177 178 // Send request 179 resp, err := http.DefaultClient.Do(req) 180 if err != nil { 181 return nil, err 182 } 183 return resp.Body, nil 184 }