github.com/Mrs4s/MiraiGo@v0.0.0-20240226124653-54bdd873e3fe/client/internal/highway/highway.go (about)

     1  package highway
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"runtime"
     7  	"sync"
     8  	"sync/atomic"
     9  	"time"
    10  
    11  	"github.com/pkg/errors"
    12  
    13  	"github.com/Mrs4s/MiraiGo/binary"
    14  	"github.com/Mrs4s/MiraiGo/client/pb"
    15  	"github.com/Mrs4s/MiraiGo/internal/proto"
    16  )
    17  
    18  // see com/tencent/mobileqq/highway/utils/BaseConstants.java#L120-L121
    19  const (
    20  	_REQ_CMD_DATA        = "PicUp.DataUp"
    21  	_REQ_CMD_HEART_BREAK = "PicUp.Echo"
    22  )
    23  
    24  type Addr struct {
    25  	IP   uint32
    26  	Port int
    27  }
    28  
    29  func (a Addr) AsNetIP() net.IP {
    30  	return net.IPv4(byte(a.IP>>24), byte(a.IP>>16), byte(a.IP>>8), byte(a.IP))
    31  }
    32  
    33  func (a Addr) String() string {
    34  	return fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(a.IP), a.Port)
    35  }
    36  
    37  func (a Addr) empty() bool {
    38  	return a.IP == 0 || a.Port == 0
    39  }
    40  
    41  type Session struct {
    42  	Uin        string
    43  	AppID      int32
    44  	SigSession []byte
    45  	SessionKey []byte
    46  
    47  	seq int32
    48  
    49  	addrMu  sync.Mutex
    50  	idx     int
    51  	SsoAddr []Addr
    52  
    53  	idleMu    sync.Mutex
    54  	idleCount int
    55  	idle      *idle
    56  }
    57  
    58  const highwayMaxResponseSize int32 = 1024 * 100 // 100k
    59  
    60  func (s *Session) AddrLength() int {
    61  	s.addrMu.Lock()
    62  	defer s.addrMu.Unlock()
    63  	return len(s.SsoAddr)
    64  }
    65  
    66  func (s *Session) AppendAddr(ip, port uint32) {
    67  	s.addrMu.Lock()
    68  	defer s.addrMu.Unlock()
    69  	addr := Addr{
    70  		IP:   ip,
    71  		Port: int(port),
    72  	}
    73  	s.SsoAddr = append(s.SsoAddr, addr)
    74  }
    75  
    76  func (s *Session) nextSeq() int32 {
    77  	return atomic.AddInt32(&s.seq, 2)
    78  }
    79  
    80  func (s *Session) sendHeartbreak(conn net.Conn) error {
    81  	head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
    82  		MsgBasehead: &pb.DataHighwayHead{
    83  			Version:   1,
    84  			Uin:       s.Uin,
    85  			Command:   _REQ_CMD_HEART_BREAK,
    86  			Seq:       s.nextSeq(),
    87  			Appid:     s.AppID,
    88  			Dataflag:  4096,
    89  			CommandId: 0,
    90  			LocaleId:  2052,
    91  		},
    92  	})
    93  	buffers := frame(head, nil)
    94  	_, err := buffers.WriteTo(conn)
    95  	return err
    96  }
    97  
    98  func (s *Session) ping(pc *persistConn) error {
    99  	start := time.Now()
   100  	err := s.sendHeartbreak(pc.conn)
   101  	if err != nil {
   102  		return errors.Wrap(err, "echo error")
   103  	}
   104  	if _, err = readResponse(binary.NewNetworkReader(pc.conn)); err != nil {
   105  		return errors.Wrap(err, "echo error")
   106  	}
   107  	// update delay
   108  	pc.ping = time.Since(start).Milliseconds()
   109  	return nil
   110  }
   111  
   112  func readResponse(r *binary.NetworkReader) (*pb.RspDataHighwayHead, error) {
   113  	_, err := r.ReadByte()
   114  	if err != nil {
   115  		return nil, errors.Wrap(err, "failed to read byte")
   116  	}
   117  	hl, _ := r.ReadInt32()
   118  	a2, _ := r.ReadInt32()
   119  	if hl > highwayMaxResponseSize || a2 > highwayMaxResponseSize {
   120  		return nil, errors.Errorf("highway response invild. head size: %v body size: %v", hl, a2)
   121  	}
   122  	head, _ := r.ReadBytes(int(hl))
   123  	_, _ = r.ReadBytes(int(a2)) // skip payload
   124  	_, _ = r.ReadByte()
   125  	rsp := new(pb.RspDataHighwayHead)
   126  	if err = proto.Unmarshal(head, rsp); err != nil {
   127  		return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
   128  	}
   129  	return rsp, nil
   130  }
   131  
   132  type persistConn struct {
   133  	conn net.Conn
   134  	addr Addr
   135  	ping int64 // echo ping
   136  }
   137  
   138  const maxIdleConn = 7
   139  
   140  type idle struct {
   141  	pc   persistConn
   142  	next *idle
   143  }
   144  
   145  // getIdleConn ...
   146  func (s *Session) getIdleConn() persistConn {
   147  	s.idleMu.Lock()
   148  	defer s.idleMu.Unlock()
   149  
   150  	// no idle
   151  	if s.idle == nil {
   152  		return persistConn{}
   153  	}
   154  
   155  	// switch the fastest idle conn
   156  	conn := s.idle.pc
   157  	s.idle = s.idle.next
   158  	s.idleCount--
   159  	if s.idleCount < 0 {
   160  		panic("idle count underflow")
   161  	}
   162  
   163  	return conn
   164  }
   165  
   166  func (s *Session) putIdleConn(pc persistConn) {
   167  	s.idleMu.Lock()
   168  	defer s.idleMu.Unlock()
   169  
   170  	// check persistConn
   171  	if pc.conn == nil || pc.addr.empty() {
   172  		panic("put bad idle conn")
   173  	}
   174  
   175  	cur := &idle{pc: pc}
   176  	s.idleCount++
   177  	if s.idle == nil { // quick path
   178  		s.idle = cur
   179  		return
   180  	}
   181  
   182  	// insert between pre and succ
   183  	var pre, succ *idle
   184  	succ = s.idle
   185  	for succ != nil && succ.pc.ping < pc.ping { // keep idle list sorted by delay incremental
   186  		pre = succ
   187  		succ = succ.next
   188  	}
   189  	if pre != nil {
   190  		pre.next = cur
   191  	}
   192  	cur.next = succ
   193  
   194  	// remove the slowest idle conn if idle count greater than maxIdleConn
   195  	if s.idleCount > maxIdleConn {
   196  		for cur.next != nil {
   197  			pre = cur
   198  			cur = cur.next
   199  		}
   200  		pre.next = nil
   201  		s.idleCount--
   202  	}
   203  }
   204  
   205  func (s *Session) connect(addr Addr) (persistConn, error) {
   206  	conn, err := net.DialTimeout("tcp", addr.String(), time.Second*3)
   207  	if err != nil {
   208  		return persistConn{}, err
   209  	}
   210  	_ = conn.(*net.TCPConn).SetKeepAlive(true)
   211  
   212  	// close conn
   213  	runtime.SetFinalizer(conn, func(conn net.Conn) {
   214  		_ = conn.Close()
   215  	})
   216  
   217  	pc := persistConn{conn: conn, addr: addr}
   218  	if err = s.ping(&pc); err != nil {
   219  		return persistConn{}, err
   220  	}
   221  	return pc, nil
   222  }
   223  
   224  func (s *Session) nextAddr() Addr {
   225  	s.addrMu.Lock()
   226  	defer s.addrMu.Unlock()
   227  	addr := s.SsoAddr[s.idx]
   228  	s.idx = (s.idx + 1) % len(s.SsoAddr)
   229  	return addr
   230  }
   231  
   232  func (s *Session) selectConn() (pc persistConn, err error) {
   233  	for { // select from idle pc
   234  		pc = s.getIdleConn()
   235  		if pc.conn == nil {
   236  			// no idle connection
   237  			break
   238  		}
   239  
   240  		err = s.ping(&pc) // ping
   241  		if err == nil {
   242  			return
   243  		}
   244  	}
   245  
   246  	try := 0
   247  	for {
   248  		addr := s.nextAddr()
   249  		pc, err = s.connect(addr)
   250  		if err == nil {
   251  			break
   252  		}
   253  		try++
   254  		if try > 5 {
   255  			break
   256  		}
   257  	}
   258  	return
   259  }