v2_handler_test.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. package icq_legacy
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "errors"
  6. "fmt"
  7. "log/slog"
  8. "net"
  9. "testing"
  10. "time"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/mock"
  13. "github.com/stretchr/testify/require"
  14. )
  15. const (
  16. testV2UIN = uint32(12345)
  17. testV2Seq = uint16(42)
  18. testV2Seq2 = uint16(7)
  19. )
  20. var testV2Addr = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 4000}
  21. type v2HandleEnv struct {
  22. T *testing.T
  23. Sender *mockPacketSender
  24. Service *mockLegacyService
  25. Sessions *LegacySessionManager
  26. Session *LegacySession
  27. }
  28. type v2HandleCase struct {
  29. name string
  30. session *LegacySession
  31. pkt []byte
  32. wire func(t *testing.T) []byte
  33. setup func(v2HandleEnv) func(error)
  34. }
  35. func v2CaseSetup(pre func(v2HandleEnv), after func(e v2HandleEnv, err error)) func(v2HandleEnv) func(error) {
  36. return func(e v2HandleEnv) func(error) {
  37. if pre != nil {
  38. pre(e)
  39. }
  40. return func(err error) {
  41. if after != nil {
  42. after(e, err)
  43. return
  44. }
  45. assert.NoError(e.T, err)
  46. }
  47. }
  48. }
  49. func runV2HandleCases(t *testing.T, cases []v2HandleCase) {
  50. t.Helper()
  51. for _, tt := range cases {
  52. t.Run(tt.name, func(t *testing.T) {
  53. sender := newMockPacketSender(t)
  54. svc := newMockLegacyService(t)
  55. sessions := newTestLegacySessionManager()
  56. e := v2HandleEnv{
  57. T: t,
  58. Sender: sender,
  59. Service: svc,
  60. Sessions: sessions,
  61. Session: tt.session,
  62. }
  63. var check func(error)
  64. if tt.setup != nil {
  65. check = tt.setup(e)
  66. } else {
  67. check = func(err error) { assert.NoError(t, err) }
  68. }
  69. h := newTestV2Handler(t, sender, svc, sessions)
  70. var wire []byte
  71. switch {
  72. case tt.wire != nil:
  73. wire = tt.wire(t)
  74. case tt.pkt != nil:
  75. wire = tt.pkt
  76. default:
  77. t.Fatal("case must set pkt or wire")
  78. }
  79. check(h.Handle(tt.session, testV2Addr, wire))
  80. })
  81. }
  82. }
  83. func newTestV2Handler(t *testing.T, sender PacketSender, svc LegacyService, sessions *LegacySessionManager) *V2Handler {
  84. t.Helper()
  85. if sessions == nil {
  86. sessions = newTestLegacySessionManager()
  87. }
  88. return NewV2Handler(sessions, svc, sender, NewV2PacketBuilder(), slog.Default())
  89. }
  90. func v2Session(uin uint32) *LegacySession {
  91. s := newTestLegacySession(uin, legacySessionOptOSCARSess, func(ls *LegacySession) {
  92. ls.Version = ICQLegacyVersionV2
  93. ls.SeqNumClient = testV2Seq
  94. })
  95. return s
  96. }
  97. func v2ServerCommand(packet []byte) uint16 {
  98. if len(packet) < 4 {
  99. return 0
  100. }
  101. return binary.LittleEndian.Uint16(packet[2:4])
  102. }
  103. func expectV2NotConnected(sender *mockPacketSender, seq uint16) {
  104. sender.EXPECT().SendPacket(testV2Addr, mock.AnythingOfType("[]uint8")).
  105. Run(func(_ *net.UDPAddr, p []byte) {
  106. if got := v2ServerCommand(p); got != ICQLegacySrvNotConnected {
  107. panic(fmt.Sprintf("got command 0x%04X, want NOT_CONNECTED", got))
  108. }
  109. if len(p) >= 6 && binary.LittleEndian.Uint16(p[4:6]) != seq {
  110. panic(fmt.Sprintf("seq %d want %d", binary.LittleEndian.Uint16(p[4:6]), seq))
  111. }
  112. }).Return(nil).Once()
  113. }
  114. func buildV2ClientPacket(pkt V2ClientPacket) []byte {
  115. isPreV2 := pkt.Command == ICQLegacyCmdFirstLogin ||
  116. pkt.Command == ICQLegacyCmdGetDeps ||
  117. pkt.Command == ICQLegacyCmdRegNewUser
  118. if isPreV2 {
  119. buf := make([]byte, 6+len(pkt.Data))
  120. binary.LittleEndian.PutUint16(buf[0:2], pkt.Version)
  121. binary.LittleEndian.PutUint16(buf[2:4], pkt.Command)
  122. binary.LittleEndian.PutUint16(buf[4:6], pkt.SeqNum)
  123. copy(buf[6:], pkt.Data)
  124. return buf
  125. }
  126. buf := make([]byte, 10+len(pkt.Data))
  127. binary.LittleEndian.PutUint16(buf[0:2], pkt.Version)
  128. binary.LittleEndian.PutUint16(buf[2:4], pkt.Command)
  129. binary.LittleEndian.PutUint16(buf[4:6], pkt.SeqNum)
  130. binary.LittleEndian.PutUint32(buf[6:10], pkt.UIN)
  131. copy(buf[10:], pkt.Data)
  132. return buf
  133. }
  134. func defaultV2Pkt(command uint16, data []byte) V2ClientPacket {
  135. return V2ClientPacket{
  136. Version: ICQLegacyVersionV2,
  137. Command: command,
  138. SeqNum: testV2Seq,
  139. UIN: testV2UIN,
  140. Data: data,
  141. }
  142. }
  143. func buildV2GetDepsWire(uin uint32, password string) []byte {
  144. pwd := []byte(password)
  145. data := make([]byte, 2+len(pwd)+4)
  146. binary.LittleEndian.PutUint16(data[0:2], uint16(len(pwd)))
  147. copy(data[2:], pwd)
  148. buf := make([]byte, 12+len(data))
  149. binary.LittleEndian.PutUint16(buf[0:2], ICQLegacyVersionV3)
  150. binary.LittleEndian.PutUint16(buf[2:4], ICQLegacyCmdGetDeps)
  151. binary.LittleEndian.PutUint16(buf[4:6], testV2Seq)
  152. binary.LittleEndian.PutUint16(buf[6:8], testV2Seq2)
  153. binary.LittleEndian.PutUint32(buf[8:12], uin)
  154. copy(buf[12:], data)
  155. return buf
  156. }
  157. func v2WriteBufLE(buf *bytes.Buffer, v any) {
  158. _ = binary.Write(buf, binary.LittleEndian, v)
  159. }
  160. func v2LoginData(password string) []byte {
  161. pwd := []byte(password)
  162. buf := make([]byte, 4+2+len(pwd)+4+4+1+2+2+12)
  163. off := 0
  164. binary.LittleEndian.PutUint32(buf[off:], 4000)
  165. off += 4
  166. binary.LittleEndian.PutUint16(buf[off:], uint16(len(pwd)))
  167. off += 2
  168. copy(buf[off:], pwd)
  169. off += len(pwd)
  170. binary.LittleEndian.PutUint32(buf[off:], 0x00040072)
  171. off += 4
  172. binary.LittleEndian.PutUint32(buf[off:], 0x7F000001)
  173. off += 4
  174. buf[off] = 0x04
  175. off++
  176. binary.LittleEndian.PutUint16(buf[off:], 0)
  177. off += 2
  178. binary.LittleEndian.PutUint16(buf[off:], 0)
  179. return buf
  180. }
  181. func v2ContactListData(uins []uint32) []byte {
  182. buf := make([]byte, 1+4*len(uins))
  183. buf[0] = byte(len(uins))
  184. for i, u := range uins {
  185. binary.LittleEndian.PutUint32(buf[1+4*i:], u)
  186. }
  187. return buf
  188. }
  189. func v2MessageData(toUIN uint32, msgType uint16, message string) []byte {
  190. msg := []byte(message)
  191. buf := make([]byte, 4+2+2+len(msg))
  192. binary.LittleEndian.PutUint32(buf[0:4], toUIN)
  193. binary.LittleEndian.PutUint16(buf[4:6], msgType)
  194. binary.LittleEndian.PutUint16(buf[6:8], uint16(len(msg)))
  195. copy(buf[8:], msg)
  196. return buf
  197. }
  198. func v2TargetUINData(target uint32) []byte {
  199. buf := make([]byte, 4)
  200. binary.LittleEndian.PutUint32(buf, target)
  201. return buf
  202. }
  203. func v2SearchUINData(clientSubSeq uint16, target uint32) []byte {
  204. buf := make([]byte, 6)
  205. binary.LittleEndian.PutUint16(buf[0:2], clientSubSeq)
  206. binary.LittleEndian.PutUint32(buf[2:6], target)
  207. return buf
  208. }
  209. func v2SearchUserData(clientSubSeq uint16, nick string) []byte {
  210. buf := new(bytes.Buffer)
  211. v2WriteBufLE(buf, clientSubSeq)
  212. writeLegacyString(buf, nick)
  213. writeLegacyString(buf, "")
  214. writeLegacyString(buf, "")
  215. writeLegacyString(buf, "")
  216. return buf.Bytes()
  217. }
  218. func v2UpdateDetailData(updateSeq uint16) []byte {
  219. buf := new(bytes.Buffer)
  220. v2WriteBufLE(buf, updateSeq)
  221. writeLegacyString(buf, "city")
  222. v2WriteBufLE(buf, uint16(840))
  223. buf.WriteByte(0)
  224. writeLegacyString(buf, "CA")
  225. v2WriteBufLE(buf, uint16(25))
  226. buf.WriteByte(1)
  227. writeLegacyString(buf, "555")
  228. writeLegacyString(buf, "http://example.com")
  229. writeLegacyString(buf, "about me")
  230. return buf.Bytes()
  231. }
  232. func v2RegNewUserData(password string) []byte {
  233. pwd := append([]byte(password), 0)
  234. buf := make([]byte, 2+2+len(pwd))
  235. binary.LittleEndian.PutUint16(buf[0:2], 0)
  236. binary.LittleEndian.PutUint16(buf[2:4], uint16(len(pwd)))
  237. copy(buf[4:], pwd)
  238. return buf
  239. }
  240. func TestV2Handler_Handle(t *testing.T) {
  241. runV2HandleCases(t, []v2HandleCase{
  242. {
  243. name: "packet too short",
  244. pkt: []byte{0x01, 0x02},
  245. setup: v2CaseSetup(nil, func(e v2HandleEnv, err error) {
  246. require.Error(e.T, err)
  247. assert.Contains(e.T, err.Error(), "packet too short")
  248. }),
  249. },
  250. {
  251. name: "nil session disallowed command",
  252. session: nil,
  253. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdKeepAlive, nil)),
  254. setup: v2CaseSetup(func(e v2HandleEnv) {
  255. expectV2NotConnected(e.Sender, testV2Seq)
  256. }, nil),
  257. },
  258. {
  259. name: "nil session ACK allowed",
  260. session: nil,
  261. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdAck, nil)),
  262. },
  263. {
  264. name: "session state updated",
  265. session: v2Session(testV2UIN),
  266. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdAck, nil)),
  267. setup: v2CaseSetup(nil, func(e v2HandleEnv, err error) {
  268. assert.NoError(e.T, err)
  269. assert.Equal(e.T, testV2Seq, e.Session.SeqNumClient)
  270. assert.WithinDuration(e.T, time.Now(), e.Session.GetLastActivity(), time.Second)
  271. }),
  272. },
  273. {
  274. name: "unknown command with session",
  275. session: v2Session(testV2UIN),
  276. pkt: buildV2ClientPacket(defaultV2Pkt(0xFFFF, nil)),
  277. setup: v2CaseSetup(func(e v2HandleEnv) {
  278. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
  279. Run(func(_ *LegacySession, p []byte) {
  280. assert.Equal(e.T, ICQLegacySrvAck, v2ServerCommand(p))
  281. }).Return(nil).Once()
  282. }, nil),
  283. },
  284. })
  285. }
  286. func TestV2Handler_HandleFirstLogin(t *testing.T) {
  287. runV2HandleCases(t, []v2HandleCase{
  288. {
  289. name: "first login",
  290. session: nil,
  291. pkt: buildV2ClientPacket(V2ClientPacket{
  292. Version: ICQLegacyVersionV2,
  293. Command: ICQLegacyCmdFirstLogin,
  294. SeqNum: testV2Seq,
  295. Data: func() []byte {
  296. b := make([]byte, 4)
  297. binary.LittleEndian.PutUint32(b, 0xCAFEBABE)
  298. return b
  299. }(),
  300. }),
  301. setup: v2CaseSetup(func(e v2HandleEnv) {
  302. e.Sender.EXPECT().SendPacket(testV2Addr, mock.AnythingOfType("[]uint8")).
  303. Run(func(_ *net.UDPAddr, p []byte) {
  304. assert.Equal(e.T, ICQLegacySrvAck, v2ServerCommand(p))
  305. }).Return(nil).Once()
  306. }, nil),
  307. },
  308. })
  309. }
  310. func TestV2Handler_HandleGetDeps(t *testing.T) {
  311. runV2HandleCases(t, []v2HandleCase{
  312. {
  313. name: "valid credentials",
  314. pkt: buildV2GetDepsWire(testV2UIN, "secret"),
  315. setup: v2CaseSetup(func(e v2HandleEnv) {
  316. e.Service.EXPECT().ValidateCredentials(mock.Anything, testV2UIN, "secret").Return(true, nil).Once()
  317. e.Sender.EXPECT().SendPacket(testV2Addr, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
  318. }, nil),
  319. },
  320. {
  321. name: "invalid credentials",
  322. pkt: buildV2GetDepsWire(testV2UIN, "wrong"),
  323. setup: v2CaseSetup(func(e v2HandleEnv) {
  324. e.Service.EXPECT().ValidateCredentials(mock.Anything, testV2UIN, "wrong").Return(false, nil).Once()
  325. e.Sender.EXPECT().SendPacket(testV2Addr, mock.AnythingOfType("[]uint8")).
  326. Run(func(_ *net.UDPAddr, p []byte) {
  327. assert.Equal(e.T, ICQLegacySrvWrongPasswd, v2ServerCommand(p))
  328. }).Return(nil).Once()
  329. }, nil),
  330. },
  331. {
  332. name: "service error",
  333. pkt: buildV2GetDepsWire(testV2UIN, "secret"),
  334. setup: v2CaseSetup(func(e v2HandleEnv) {
  335. e.Service.EXPECT().ValidateCredentials(mock.Anything, testV2UIN, "secret").
  336. Return(false, errors.New("db down")).Once()
  337. e.Sender.EXPECT().SendPacket(testV2Addr, mock.AnythingOfType("[]uint8")).
  338. Run(func(_ *net.UDPAddr, p []byte) {
  339. assert.Equal(e.T, ICQLegacySrvWrongPasswd, v2ServerCommand(p))
  340. }).Return(nil).Once()
  341. }, nil),
  342. },
  343. })
  344. }
  345. func TestV2Handler_HandleLogin(t *testing.T) {
  346. runV2HandleCases(t, []v2HandleCase{
  347. {
  348. name: "auth success",
  349. session: nil,
  350. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdLogin, v2LoginData("secret"))),
  351. setup: v2CaseSetup(func(e v2HandleEnv) {
  352. e.Service.EXPECT().AuthenticateUser(mock.Anything, mock.MatchedBy(func(req AuthRequest) bool {
  353. return req.UIN == testV2UIN && req.Password == "secret"
  354. })).Return(&AuthResult{Success: true, oscarSession: testAuthSuccessInstance()}, nil).Once()
  355. e.Service.EXPECT().NotifyUserOnline(mock.Anything, testV2UIN, mock.Anything).Return(nil).Once()
  356. e.Sender.EXPECT().SendToSession(mock.AnythingOfType("*icq_legacy.LegacySession"), mock.AnythingOfType("[]uint8")).Return(nil).Twice()
  357. }, func(e v2HandleEnv, err error) {
  358. assert.NoError(e.T, err)
  359. assert.NotNil(e.T, e.Sessions.GetSession(testV2UIN))
  360. }),
  361. },
  362. {
  363. name: "bad password",
  364. session: nil,
  365. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdLogin, v2LoginData("wrong"))),
  366. setup: v2CaseSetup(func(e v2HandleEnv) {
  367. e.Service.EXPECT().AuthenticateUser(mock.Anything, mock.Anything).
  368. Return(&AuthResult{Success: false, ErrorCode: 0x0001}, nil).Once()
  369. e.Sender.EXPECT().SendPacket(testV2Addr, mock.AnythingOfType("[]uint8")).
  370. Run(func(_ *net.UDPAddr, p []byte) {
  371. assert.Equal(e.T, ICQLegacySrvWrongPasswd, v2ServerCommand(p))
  372. }).Return(nil).Once()
  373. }, nil),
  374. },
  375. {
  376. name: "parse error",
  377. session: nil,
  378. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdLogin, []byte{0, 0, 0})),
  379. setup: v2CaseSetup(func(e v2HandleEnv) {
  380. e.Sender.EXPECT().SendPacket(testV2Addr, mock.AnythingOfType("[]uint8")).
  381. Run(func(_ *net.UDPAddr, p []byte) {
  382. assert.Equal(e.T, ICQLegacySrvWrongPasswd, v2ServerCommand(p))
  383. }).Return(nil).Once()
  384. }, nil),
  385. },
  386. })
  387. }
  388. func TestV2Handler_HandleContactList(t *testing.T) {
  389. runV2HandleCases(t, []v2HandleCase{
  390. {
  391. name: "valid contact list",
  392. session: v2Session(testV2UIN),
  393. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdContactList, v2ContactListData([]uint32{99999}))),
  394. setup: v2CaseSetup(func(e v2HandleEnv) {
  395. e.Service.EXPECT().ProcessContactList(mock.Anything, mock.Anything, mock.Anything).
  396. Return(&ContactListResult{}, nil).Once()
  397. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  398. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
  399. Run(func(_ *LegacySession, p []byte) {
  400. assert.Equal(e.T, ICQLegacySrvUserListDone, v2ServerCommand(p))
  401. }).Return(nil).Once()
  402. }, nil),
  403. },
  404. {
  405. name: "nil session",
  406. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdContactList, v2ContactListData(nil))),
  407. setup: v2CaseSetup(func(e v2HandleEnv) {
  408. expectV2NotConnected(e.Sender, testV2Seq)
  409. }, nil),
  410. },
  411. })
  412. }
  413. func TestV2Handler_HandlePing(t *testing.T) {
  414. runV2HandleCases(t, []v2HandleCase{
  415. {
  416. name: "keep alive",
  417. session: v2Session(testV2UIN),
  418. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdKeepAlive, nil)),
  419. setup: v2CaseSetup(func(e v2HandleEnv) {
  420. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
  421. Run(func(_ *LegacySession, p []byte) {
  422. assert.Equal(e.T, ICQLegacySrvAck, v2ServerCommand(p))
  423. }).Return(nil).Once()
  424. }, nil),
  425. },
  426. {
  427. name: "nil session",
  428. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdKeepAlive, nil)),
  429. setup: v2CaseSetup(func(e v2HandleEnv) {
  430. expectV2NotConnected(e.Sender, testV2Seq)
  431. }, nil),
  432. },
  433. })
  434. }
  435. func TestV2Handler_HandleLogoff(t *testing.T) {
  436. runV2HandleCases(t, []v2HandleCase{
  437. {
  438. name: "active session",
  439. session: v2Session(testV2UIN),
  440. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdLogoff, nil)),
  441. setup: v2CaseSetup(func(e v2HandleEnv) {
  442. registerSession(e.Sessions, e.Session)
  443. e.Service.EXPECT().NotifyUserOffline(mock.Anything, testV2UIN).Return(nil).Once()
  444. }, func(e v2HandleEnv, err error) {
  445. assert.NoError(e.T, err)
  446. assert.Nil(e.T, e.Sessions.GetSession(testV2UIN))
  447. }),
  448. },
  449. {
  450. name: "nil session",
  451. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdLogoff, nil)),
  452. setup: v2CaseSetup(func(e v2HandleEnv) {
  453. expectV2NotConnected(e.Sender, testV2Seq)
  454. }, nil),
  455. },
  456. })
  457. }
  458. func TestV2Handler_HandleSetStatus(t *testing.T) {
  459. runV2HandleCases(t, []v2HandleCase{
  460. {
  461. name: "valid status",
  462. session: v2Session(testV2UIN),
  463. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdSetStatus, func() []byte {
  464. b := make([]byte, 4)
  465. binary.LittleEndian.PutUint32(b, 0x00000001)
  466. return b
  467. }())),
  468. setup: v2CaseSetup(func(e v2HandleEnv) {
  469. e.Service.EXPECT().ProcessStatusChange(mock.Anything, mock.Anything).
  470. Return(&StatusChangeResult{}, nil).Once()
  471. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  472. }, func(e v2HandleEnv, err error) {
  473. assert.NoError(e.T, err)
  474. assert.Equal(e.T, uint32(0x00000001), e.Session.GetStatus())
  475. }),
  476. },
  477. {
  478. name: "nil session",
  479. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdSetStatus, func() []byte {
  480. b := make([]byte, 4)
  481. binary.LittleEndian.PutUint32(b, 1)
  482. return b
  483. }())),
  484. setup: v2CaseSetup(func(e v2HandleEnv) {
  485. expectV2NotConnected(e.Sender, testV2Seq)
  486. }, nil),
  487. },
  488. })
  489. }
  490. func TestV2Handler_HandleMessage(t *testing.T) {
  491. target := v2Session(99999)
  492. runV2HandleCases(t, []v2HandleCase{
  493. {
  494. name: "offline stored",
  495. session: v2Session(testV2UIN),
  496. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdThruServer, v2MessageData(99999, 0x0001, "hi"))),
  497. setup: v2CaseSetup(func(e v2HandleEnv) {
  498. e.Service.EXPECT().ProcessMessage(mock.Anything, mock.Anything, mock.Anything).
  499. Return(&MessageResult{StoredOffline: true}, nil).Once()
  500. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  501. }, nil),
  502. },
  503. {
  504. name: "online target",
  505. session: v2Session(testV2UIN),
  506. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdThruServer, v2MessageData(99999, 0x0001, "hi"))),
  507. setup: v2CaseSetup(func(e v2HandleEnv) {
  508. registerSession(e.Sessions, target)
  509. e.Service.EXPECT().ProcessMessage(mock.Anything, mock.Anything, mock.Anything).
  510. Return(&MessageResult{TargetOnline: true}, nil).Once()
  511. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  512. e.Sender.EXPECT().SendToSession(mock.AnythingOfType("*icq_legacy.LegacySession"), mock.AnythingOfType("[]uint8")).Return(nil).Once()
  513. }, nil),
  514. },
  515. {
  516. name: "nil session",
  517. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdThruServer, v2MessageData(99999, 0x0001, "hi"))),
  518. setup: v2CaseSetup(func(e v2HandleEnv) {
  519. expectV2NotConnected(e.Sender, testV2Seq)
  520. }, nil),
  521. },
  522. })
  523. }
  524. func TestV2Handler_HandleUserAdd(t *testing.T) {
  525. runV2HandleCases(t, []v2HandleCase{
  526. {
  527. name: "success",
  528. session: v2Session(testV2UIN),
  529. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdUserAdd, v2TargetUINData(99999))),
  530. setup: v2CaseSetup(func(e v2HandleEnv) {
  531. e.Service.EXPECT().ProcessUserAdd(mock.Anything, mock.Anything, mock.Anything).
  532. Return(&UserAddResult{}, nil).Once()
  533. e.Service.EXPECT().GetUserInfoForProtocol(mock.Anything, uint32(99999)).Return(nil, nil).Once()
  534. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  535. }, func(e v2HandleEnv, err error) {
  536. assert.Contains(e.T, e.Session.GetContactList(), uint32(99999))
  537. }),
  538. },
  539. {
  540. name: "nil session",
  541. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdUserAdd, v2TargetUINData(99999))),
  542. setup: v2CaseSetup(func(e v2HandleEnv) {
  543. expectV2NotConnected(e.Sender, testV2Seq)
  544. }, nil),
  545. },
  546. })
  547. }
  548. func TestV2Handler_HandleSearchUIN(t *testing.T) {
  549. runV2HandleCases(t, []v2HandleCase{
  550. {
  551. name: "found",
  552. session: v2Session(testV2UIN),
  553. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdSearchUIN, v2SearchUINData(1, 99999))),
  554. setup: v2CaseSetup(func(e v2HandleEnv) {
  555. e.Service.EXPECT().SearchByUIN(mock.Anything, uint32(99999)).
  556. Return(&LegacyUserSearchResult{UIN: 99999}, nil).Once()
  557. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(3)
  558. }, nil),
  559. },
  560. {
  561. name: "not found",
  562. session: v2Session(testV2UIN),
  563. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdSearchUIN, v2SearchUINData(1, 99999))),
  564. setup: v2CaseSetup(func(e v2HandleEnv) {
  565. e.Service.EXPECT().SearchByUIN(mock.Anything, uint32(99999)).
  566. Return(&LegacyUserSearchResult{}, nil).Once()
  567. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
  568. }, nil),
  569. },
  570. })
  571. }
  572. func TestV2Handler_HandleVisibleList(t *testing.T) {
  573. runV2HandleCases(t, []v2HandleCase{
  574. {
  575. name: "valid list",
  576. session: v2Session(testV2UIN),
  577. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdVisibleList, v2ContactListData([]uint32{11111}))),
  578. setup: v2CaseSetup(func(e v2HandleEnv) {
  579. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
  580. Run(func(_ *LegacySession, p []byte) {
  581. assert.Equal(e.T, ICQLegacySrvAck, v2ServerCommand(p))
  582. }).Return(nil).Once()
  583. }, func(e v2HandleEnv, err error) {
  584. assert.True(e.T, e.Session.IsOnVisibleList(11111))
  585. }),
  586. },
  587. })
  588. }
  589. func TestV2Handler_HandleInvisibleList(t *testing.T) {
  590. runV2HandleCases(t, []v2HandleCase{
  591. {
  592. name: "valid list",
  593. session: v2Session(testV2UIN),
  594. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdInvisibleList, v2ContactListData([]uint32{22222}))),
  595. setup: v2CaseSetup(func(e v2HandleEnv) {
  596. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  597. }, func(e v2HandleEnv, err error) {
  598. assert.True(e.T, e.Session.IsOnInvisibleList(22222))
  599. }),
  600. },
  601. })
  602. }
  603. func TestV2Handler_HandleOfflineMsgReq(t *testing.T) {
  604. runV2HandleCases(t, []v2HandleCase{
  605. {
  606. name: "no messages",
  607. session: v2Session(testV2UIN),
  608. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdSysMsgReq, nil)),
  609. setup: v2CaseSetup(func(e v2HandleEnv) {
  610. e.Service.EXPECT().GetOfflineMessages(mock.Anything, testV2UIN).
  611. Return([]LegacyOfflineMessage{}, nil).Once()
  612. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
  613. }, nil),
  614. },
  615. })
  616. }
  617. func TestV2Handler_HandleOfflineMsgAck(t *testing.T) {
  618. runV2HandleCases(t, []v2HandleCase{
  619. {
  620. name: "ack",
  621. session: v2Session(testV2UIN),
  622. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdSysMsgDoneAck, nil)),
  623. setup: v2CaseSetup(func(e v2HandleEnv) {
  624. e.Service.EXPECT().AckOfflineMessages(mock.Anything, testV2UIN).Return(nil).Once()
  625. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  626. }, nil),
  627. },
  628. })
  629. }
  630. func v2InfoReqData(targetUIN uint32) []byte {
  631. buf := make([]byte, 6)
  632. binary.LittleEndian.PutUint16(buf[0:2], testV2Seq)
  633. binary.LittleEndian.PutUint32(buf[2:6], targetUIN)
  634. return buf
  635. }
  636. func TestV2Handler_HandleInfoReq(t *testing.T) {
  637. runV2HandleCases(t, []v2HandleCase{
  638. {
  639. name: "get info",
  640. session: v2Session(testV2UIN),
  641. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdInfoReq, v2InfoReqData(99999))),
  642. setup: v2CaseSetup(func(e v2HandleEnv) {
  643. e.Service.EXPECT().GetUserInfo(mock.Anything, uint32(99999)).
  644. Return(&LegacyUserSearchResult{UIN: 99999, Nickname: "nick"}, nil).Once()
  645. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
  646. }, nil),
  647. },
  648. })
  649. }
  650. func TestV2Handler_HandleExtInfoReq(t *testing.T) {
  651. runV2HandleCases(t, []v2HandleCase{
  652. {
  653. name: "get ext info",
  654. session: v2Session(testV2UIN),
  655. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdExtInfoReq, v2InfoReqData(99999))),
  656. setup: v2CaseSetup(func(e v2HandleEnv) {
  657. e.Service.EXPECT().GetFullUserInfo(mock.Anything, uint32(99999)).Return(testMinimalUser(), nil).Once()
  658. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
  659. }, nil),
  660. },
  661. })
  662. }
  663. func TestV2Handler_HandleUpdateBasic(t *testing.T) {
  664. runV2HandleCases(t, []v2HandleCase{
  665. {
  666. name: "success",
  667. session: v2Session(testV2UIN),
  668. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdUpdateBasic, func() []byte {
  669. buf := new(bytes.Buffer)
  670. v2WriteBufLE(buf, testV2Seq)
  671. writeLegacyString(buf, "nick")
  672. writeLegacyString(buf, "first")
  673. writeLegacyString(buf, "last")
  674. writeLegacyString(buf, "a@b.com")
  675. buf.WriteByte(0) // auth
  676. return buf.Bytes()
  677. }())),
  678. setup: v2CaseSetup(func(e v2HandleEnv) {
  679. e.Service.EXPECT().GetFullUserInfo(mock.Anything, testV2UIN).Return(testMinimalUser(), nil).Once()
  680. e.Service.EXPECT().UpdateBasicInfo(mock.Anything, testV2UIN, mock.Anything).Return(nil).Once()
  681. e.Service.EXPECT().SetAuthMode(mock.Anything, testV2UIN, false).Return(nil).Once()
  682. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
  683. }, nil),
  684. },
  685. })
  686. }
  687. func TestV2Handler_HandleSearchUser(t *testing.T) {
  688. runV2HandleCases(t, []v2HandleCase{
  689. {
  690. name: "found",
  691. session: v2Session(testV2UIN),
  692. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdSearchUser, v2SearchUserData(1, "alice"))),
  693. setup: v2CaseSetup(func(e v2HandleEnv) {
  694. e.Service.EXPECT().SearchByName(mock.Anything, "alice", "", "", "").
  695. Return([]LegacyUserSearchResult{{UIN: 99999, Nickname: "alice"}}, nil).Once()
  696. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(3)
  697. }, nil),
  698. },
  699. {
  700. name: "not found",
  701. session: v2Session(testV2UIN),
  702. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdSearchUser, v2SearchUserData(1, "nobody"))),
  703. setup: v2CaseSetup(func(e v2HandleEnv) {
  704. e.Service.EXPECT().SearchByName(mock.Anything, "nobody", "", "", "").
  705. Return(nil, nil).Once()
  706. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
  707. }, nil),
  708. },
  709. })
  710. }
  711. func TestV2Handler_HandleAuthorize(t *testing.T) {
  712. runV2HandleCases(t, []v2HandleCase{
  713. {
  714. name: "authorize message",
  715. session: v2Session(testV2UIN),
  716. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdAuthorize, v2MessageData(99999, 0x0001, "auth"))),
  717. setup: v2CaseSetup(func(e v2HandleEnv) {
  718. e.Service.EXPECT().ProcessMessage(mock.Anything, mock.Anything, mock.Anything).
  719. Return(&MessageResult{StoredOffline: true}, nil).Once()
  720. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  721. }, nil),
  722. },
  723. })
  724. }
  725. func TestV2Handler_HandleUpdateDetail(t *testing.T) {
  726. runV2HandleCases(t, []v2HandleCase{
  727. {
  728. name: "success",
  729. session: v2Session(testV2UIN),
  730. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdUpdateDetail, v2UpdateDetailData(3))),
  731. setup: v2CaseSetup(func(e v2HandleEnv) {
  732. e.Service.EXPECT().GetFullUserInfo(mock.Anything, testV2UIN).Return(testMinimalUser(), nil).Once()
  733. e.Service.EXPECT().UpdateBasicInfo(mock.Anything, testV2UIN, mock.Anything).Return(nil).Once()
  734. e.Service.EXPECT().UpdateMoreInfo(mock.Anything, testV2UIN, mock.Anything).Return(nil).Once()
  735. e.Service.EXPECT().SetNotes(mock.Anything, testV2UIN, "about me").Return(nil).Once()
  736. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
  737. }, nil),
  738. },
  739. })
  740. }
  741. func TestV2Handler_HandleRegNewUser(t *testing.T) {
  742. runV2HandleCases(t, []v2HandleCase{
  743. {
  744. name: "success",
  745. pkt: buildV2ClientPacket(V2ClientPacket{
  746. Version: ICQLegacyVersionV2,
  747. Command: ICQLegacyCmdRegNewUser,
  748. SeqNum: testV2Seq,
  749. Data: v2RegNewUserData("newpass"),
  750. }),
  751. setup: v2CaseSetup(func(e v2HandleEnv) {
  752. e.Service.EXPECT().RegisterNewUser(mock.Anything, "", "", "", "", "newpass").
  753. Return(uint32(100001), nil).Once()
  754. e.Sender.EXPECT().SendPacket(testV2Addr, mock.AnythingOfType("[]uint8")).
  755. Run(func(_ *net.UDPAddr, p []byte) {
  756. assert.Equal(e.T, ICQLegacySrvNewUIN, v2ServerCommand(p))
  757. }).Return(nil).Once()
  758. }, nil),
  759. },
  760. })
  761. }
  762. func TestV2Handler_HandleSetStatus_notifiesContacts(t *testing.T) {
  763. const contactUIN = uint32(88888)
  764. changer := v2Session(testV2UIN)
  765. contact := v2Session(contactUIN)
  766. sender := newMockPacketSender(t)
  767. svc := newMockLegacyService(t)
  768. sessions := newTestLegacySessionManager()
  769. registerSession(sessions, changer)
  770. registerSession(sessions, contact)
  771. svc.EXPECT().ProcessStatusChange(mock.Anything, mock.Anything).
  772. Return(&StatusChangeResult{NotifyTargets: []NotifyTarget{{UIN: contactUIN}}}, nil).Once()
  773. sender.EXPECT().SendToSession(changer, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  774. sender.EXPECT().SendToSession(contact, mock.MatchedBy(func(p []byte) bool {
  775. return v2ServerCommand(p) == ICQLegacySrvUserStatus
  776. })).Return(nil).Once()
  777. h := newTestV2Handler(t, sender, svc, sessions)
  778. pkt := buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdSetStatus, func() []byte {
  779. b := make([]byte, 4)
  780. binary.LittleEndian.PutUint32(b, 0x00000002)
  781. return b
  782. }()))
  783. require.NoError(t, h.Handle(changer, testV2Addr, pkt))
  784. }
  785. func TestV2Handler_HandleUserAdd_notifyTarget(t *testing.T) {
  786. const targetUIN = uint32(99999)
  787. adder := v2Session(testV2UIN)
  788. target := v2Session(targetUIN)
  789. sender := newMockPacketSender(t)
  790. svc := newMockLegacyService(t)
  791. sessions := newTestLegacySessionManager()
  792. registerSession(sessions, adder)
  793. registerSession(sessions, target)
  794. svc.EXPECT().ProcessUserAdd(mock.Anything, mock.Anything, mock.Anything).Return(&UserAddResult{}, nil).Once()
  795. sender.EXPECT().SendToSession(adder, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  796. sender.EXPECT().SendToSession(adder, mock.MatchedBy(func(p []byte) bool {
  797. return v2ServerCommand(p) == ICQLegacySrvUserOnline
  798. })).Return(nil).Once()
  799. sender.EXPECT().SendToSession(target, mock.MatchedBy(func(p []byte) bool {
  800. return v2ServerCommand(p) == ICQLegacySrvUserOnline
  801. })).Return(nil).Once()
  802. h := newTestV2Handler(t, sender, svc, sessions)
  803. pkt := buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdUserAdd, v2TargetUINData(targetUIN)))
  804. require.NoError(t, h.Handle(adder, testV2Addr, pkt))
  805. assert.Contains(t, adder.GetContactList(), targetUIN)
  806. }
  807. func TestV2Handler_HandleLogoff_notifiesContacts(t *testing.T) {
  808. const contactUIN = uint32(88888)
  809. loggingOff := v2Session(testV2UIN)
  810. loggingOff.SetContactList([]uint32{contactUIN})
  811. contact := v2Session(contactUIN)
  812. sender := newMockPacketSender(t)
  813. svc := newMockLegacyService(t)
  814. sessions := newTestLegacySessionManager()
  815. registerSession(sessions, loggingOff)
  816. registerSession(sessions, contact)
  817. svc.EXPECT().NotifyUserOffline(mock.Anything, testV2UIN).Return(nil).Once()
  818. sender.EXPECT().SendToSession(contact, mock.MatchedBy(func(p []byte) bool {
  819. return v2ServerCommand(p) == ICQLegacySrvUserOffline
  820. })).Return(nil).Once()
  821. h := newTestV2Handler(t, sender, svc, sessions)
  822. pkt := buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdLogoff, nil))
  823. require.NoError(t, h.Handle(loggingOff, testV2Addr, pkt))
  824. }