github.com/LagrangeDev/LagrangeGo@v0.0.0-20240512064304-ad4a85e10cb4/client/internal/highway/highway.go (about)

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