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 }