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  }