oservice_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. package server
  2. import (
  3. "bytes"
  4. "github.com/stretchr/testify/mock"
  5. "testing"
  6. "time"
  7. "github.com/mkaminski/goaim/oscar"
  8. "github.com/stretchr/testify/assert"
  9. )
  10. func TestReceiveAndSendServiceRequest(t *testing.T) {
  11. cases := []struct {
  12. // name is the unit test name
  13. name string
  14. // cfg is the application config
  15. cfg Config
  16. // chatRoom is the chat room the user connects to
  17. chatRoom *ChatRoom
  18. // userSession is the session of the user requesting the chat service
  19. // info
  20. userSession *Session
  21. // inputSNAC is the SNAC sent by the sender client
  22. inputSNAC oscar.SNAC_0x01_0x04_OServiceServiceRequest
  23. // expectSNACFrame is the SNAC frame sent from the server to the recipient
  24. // client
  25. expectOutput XMessage
  26. // expectErr is the expected error returned by the router
  27. expectErr error
  28. }{
  29. {
  30. name: "request info for ICBM service, return invalid SNAC err",
  31. userSession: &Session{
  32. ScreenName: "user_screen_name",
  33. },
  34. inputSNAC: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
  35. FoodGroup: oscar.ICBM,
  36. },
  37. expectErr: ErrUnsupportedSubGroup,
  38. },
  39. {
  40. name: "request info for connecting to chat room, return chat service and chat room metadata",
  41. cfg: Config{
  42. OSCARHost: "127.0.0.1",
  43. ChatPort: 1234,
  44. },
  45. chatRoom: &ChatRoom{
  46. CreateTime: time.UnixMilli(0),
  47. DetailLevel: 4,
  48. Exchange: 8,
  49. Cookie: "the-chat-cookie",
  50. InstanceNumber: 16,
  51. Name: "my new chat",
  52. },
  53. userSession: &Session{
  54. ID: "user-sess-id",
  55. ScreenName: "user_screen_name",
  56. },
  57. inputSNAC: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
  58. FoodGroup: oscar.CHAT,
  59. TLVRestBlock: oscar.TLVRestBlock{
  60. TLVList: oscar.TLVList{
  61. {
  62. TType: 0x01,
  63. Val: oscar.SNAC_0x01_0x04_TLVRoomInfo{
  64. Exchange: 8,
  65. Cookie: []byte("the-chat-cookie"),
  66. InstanceNumber: 16,
  67. },
  68. },
  69. },
  70. },
  71. },
  72. expectOutput: XMessage{
  73. snacFrame: oscar.SnacFrame{
  74. FoodGroup: oscar.OSERVICE,
  75. SubGroup: oscar.OServiceServiceResponse,
  76. },
  77. snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
  78. TLVRestBlock: oscar.TLVRestBlock{
  79. TLVList: oscar.TLVList{
  80. {
  81. TType: oscar.OServiceTLVTagsReconnectHere,
  82. Val: "127.0.0.1:1234",
  83. },
  84. {
  85. TType: oscar.OServiceTLVTagsLoginCookie,
  86. Val: ChatCookie{
  87. Cookie: []byte("the-chat-cookie"),
  88. SessID: "user-sess-id",
  89. },
  90. },
  91. {
  92. TType: oscar.OServiceTLVTagsGroupID,
  93. Val: oscar.CHAT,
  94. },
  95. {
  96. TType: oscar.OServiceTLVTagsSSLCertName,
  97. Val: "",
  98. },
  99. {
  100. TType: oscar.OServiceTLVTagsSSLState,
  101. Val: uint8(0x00),
  102. },
  103. },
  104. },
  105. },
  106. },
  107. },
  108. {
  109. name: "request info for connecting to non-existent chat room, return SNAC error",
  110. cfg: Config{
  111. OSCARHost: "127.0.0.1",
  112. ChatPort: 1234,
  113. },
  114. chatRoom: nil,
  115. userSession: &Session{
  116. ID: "user-sess-id",
  117. ScreenName: "user_screen_name",
  118. },
  119. inputSNAC: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
  120. FoodGroup: oscar.CHAT,
  121. TLVRestBlock: oscar.TLVRestBlock{
  122. TLVList: oscar.TLVList{
  123. {
  124. TType: 0x01,
  125. Val: oscar.SNAC_0x01_0x04_TLVRoomInfo{
  126. Exchange: 8,
  127. Cookie: []byte("the-chat-cookie"),
  128. InstanceNumber: 16,
  129. },
  130. },
  131. },
  132. },
  133. },
  134. expectErr: ErrUnsupportedSubGroup,
  135. },
  136. }
  137. for _, tc := range cases {
  138. t.Run(tc.name, func(t *testing.T) {
  139. //
  140. // initialize dependencies
  141. //
  142. sm := NewMockSessionManager(t)
  143. cr := NewChatRegistry()
  144. if tc.chatRoom != nil {
  145. sm.EXPECT().
  146. NewSessionWithSN(tc.userSession.ID, tc.userSession.ScreenName).
  147. Return(&Session{}).
  148. Maybe()
  149. tc.chatRoom.SessionManager = sm
  150. cr.Register(*tc.chatRoom)
  151. }
  152. //
  153. // send input SNAC
  154. //
  155. assert.NoError(t, tc.inputSNAC.SerializeInPlace())
  156. svc := OServiceService{}
  157. outputSNAC, err := svc.ServiceRequestHandler(tc.cfg, cr, tc.userSession, tc.inputSNAC)
  158. assert.ErrorIs(t, err, tc.expectErr)
  159. if tc.expectErr != nil {
  160. return
  161. }
  162. //
  163. // verify output
  164. //
  165. assert.Equal(t, tc.expectOutput, outputSNAC)
  166. })
  167. }
  168. }
  169. func TestOServiceRouter_RouteOService(t *testing.T) {
  170. cases := []struct {
  171. // name is the unit test name
  172. name string
  173. // input is the request payload
  174. input XMessage
  175. // output is the response payload
  176. output XMessage
  177. // handlerErr is the mocked handler error response
  178. handlerErr error
  179. // expectErr is the expected error returned by the router
  180. expectErr error
  181. }{
  182. {
  183. name: "receive OServiceClientOnline, return no response",
  184. input: XMessage{
  185. snacFrame: oscar.SnacFrame{
  186. FoodGroup: oscar.OSERVICE,
  187. SubGroup: oscar.OServiceClientOnline,
  188. },
  189. snacOut: oscar.SNAC_0x01_0x02_OServiceClientOnline{
  190. GroupVersions: []struct {
  191. FoodGroup uint16
  192. Version uint16
  193. ToolID uint16
  194. ToolVersion uint16
  195. }{
  196. {
  197. FoodGroup: 10,
  198. },
  199. },
  200. },
  201. },
  202. output: XMessage{},
  203. },
  204. {
  205. name: "receive OServiceServiceRequest, return OServiceServiceResponse",
  206. input: XMessage{
  207. snacFrame: oscar.SnacFrame{
  208. FoodGroup: oscar.OSERVICE,
  209. SubGroup: oscar.OServiceServiceRequest,
  210. },
  211. snacOut: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
  212. FoodGroup: 10,
  213. },
  214. },
  215. output: XMessage{
  216. snacFrame: oscar.SnacFrame{
  217. FoodGroup: oscar.OSERVICE,
  218. SubGroup: oscar.OServiceServiceResponse,
  219. },
  220. snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
  221. TLVRestBlock: oscar.TLVRestBlock{
  222. TLVList: oscar.TLVList{
  223. {
  224. TType: 0x01,
  225. Val: uint16(1000),
  226. },
  227. },
  228. },
  229. },
  230. },
  231. },
  232. {
  233. name: "receive OServiceRateParamsQuery, return OServiceRateParamsReply",
  234. input: XMessage{
  235. snacFrame: oscar.SnacFrame{
  236. FoodGroup: oscar.OSERVICE,
  237. SubGroup: oscar.OServiceRateParamsQuery,
  238. },
  239. snacOut: struct{}{},
  240. },
  241. output: XMessage{
  242. snacFrame: oscar.SnacFrame{
  243. FoodGroup: oscar.OSERVICE,
  244. SubGroup: oscar.OServiceRateParamsReply,
  245. },
  246. snacOut: oscar.SNAC_0x01_0x07_OServiceRateParamsReply{
  247. RateGroups: []struct {
  248. ID uint16
  249. Pairs []struct {
  250. FoodGroup uint16
  251. SubGroup uint16
  252. } `count_prefix:"uint16"`
  253. }{
  254. {
  255. ID: 1,
  256. },
  257. },
  258. },
  259. },
  260. },
  261. {
  262. name: "receive OServiceRateParamsSubAdd, return no response",
  263. input: XMessage{
  264. snacFrame: oscar.SnacFrame{
  265. FoodGroup: oscar.OSERVICE,
  266. SubGroup: oscar.OServiceRateParamsSubAdd,
  267. },
  268. snacOut: oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
  269. TLVRestBlock: oscar.TLVRestBlock{
  270. TLVList: oscar.TLVList{
  271. {
  272. TType: 0x01,
  273. Val: []byte{1, 2, 3, 4},
  274. },
  275. },
  276. },
  277. },
  278. },
  279. output: XMessage{},
  280. },
  281. {
  282. name: "receive OServiceUserInfoQuery, return OServiceUserInfoUpdate",
  283. input: XMessage{
  284. snacFrame: oscar.SnacFrame{
  285. FoodGroup: oscar.OSERVICE,
  286. SubGroup: oscar.OServiceUserInfoQuery,
  287. },
  288. snacOut: struct{}{},
  289. },
  290. output: XMessage{
  291. snacFrame: oscar.SnacFrame{
  292. FoodGroup: oscar.OSERVICE,
  293. SubGroup: oscar.OServiceUserInfoUpdate,
  294. },
  295. snacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
  296. TLVUserInfo: oscar.TLVUserInfo{
  297. ScreenName: "screen-name",
  298. },
  299. },
  300. },
  301. },
  302. {
  303. name: "receive OServiceIdleNotification, return no response",
  304. input: XMessage{
  305. snacFrame: oscar.SnacFrame{
  306. FoodGroup: oscar.OSERVICE,
  307. SubGroup: oscar.OServiceIdleNotification,
  308. },
  309. snacOut: oscar.SNAC_0x01_0x11_OServiceIdleNotification{
  310. IdleTime: 10,
  311. },
  312. },
  313. output: XMessage{},
  314. },
  315. {
  316. name: "receive OServiceClientVersions, return OServiceHostVersions",
  317. input: XMessage{
  318. snacFrame: oscar.SnacFrame{
  319. FoodGroup: oscar.OSERVICE,
  320. SubGroup: oscar.OServiceClientVersions,
  321. },
  322. snacOut: oscar.SNAC_0x01_0x17_OServiceClientVersions{
  323. Versions: []uint16{
  324. 10,
  325. },
  326. },
  327. },
  328. output: XMessage{
  329. snacFrame: oscar.SnacFrame{
  330. FoodGroup: oscar.OSERVICE,
  331. SubGroup: oscar.OServiceHostVersions,
  332. },
  333. snacOut: oscar.SNAC_0x01_0x18_OServiceHostVersions{
  334. Versions: []uint16{
  335. 10,
  336. },
  337. },
  338. },
  339. },
  340. {
  341. name: "receive OServiceSetUserInfoFields, return OServiceUserInfoUpdate",
  342. input: XMessage{
  343. snacFrame: oscar.SnacFrame{
  344. FoodGroup: oscar.OSERVICE,
  345. SubGroup: oscar.OServiceSetUserInfoFields,
  346. },
  347. snacOut: oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
  348. TLVRestBlock: oscar.TLVRestBlock{
  349. TLVList: oscar.TLVList{
  350. {
  351. TType: 0x01,
  352. Val: []byte{1, 2, 3, 4},
  353. },
  354. },
  355. },
  356. },
  357. },
  358. output: XMessage{
  359. snacFrame: oscar.SnacFrame{
  360. FoodGroup: oscar.OSERVICE,
  361. SubGroup: oscar.OServiceUserInfoUpdate,
  362. },
  363. snacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
  364. TLVUserInfo: oscar.TLVUserInfo{
  365. ScreenName: "screen-name",
  366. },
  367. },
  368. },
  369. },
  370. {
  371. name: "receive OServicePauseReq, expect ErrUnsupportedSubGroup",
  372. input: XMessage{
  373. snacFrame: oscar.SnacFrame{
  374. FoodGroup: oscar.OSERVICE,
  375. SubGroup: oscar.OServicePauseReq,
  376. },
  377. snacOut: struct{}{}, // empty SNAC
  378. },
  379. output: XMessage{}, // empty SNAC
  380. expectErr: ErrUnsupportedSubGroup,
  381. },
  382. }
  383. for _, tc := range cases {
  384. t.Run(tc.name, func(t *testing.T) {
  385. svc := NewMockOServiceHandler(t)
  386. svc.EXPECT().
  387. ServiceRequestHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
  388. Return(tc.output, tc.handlerErr).
  389. Maybe()
  390. svc.EXPECT().
  391. RateParamsQueryHandler().
  392. Return(tc.output).
  393. Maybe()
  394. svc.EXPECT().
  395. UserInfoQueryHandler(mock.Anything).
  396. Return(tc.output).
  397. Maybe()
  398. svc.EXPECT().
  399. ClientVersionsHandler(tc.input.snacOut).
  400. Return(tc.output).
  401. Maybe()
  402. svc.EXPECT().
  403. SetUserInfoFieldsHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
  404. Return(tc.output, tc.handlerErr).
  405. Maybe()
  406. svc.EXPECT().
  407. ClientOnlineHandler(tc.input.snacOut, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
  408. Return(tc.handlerErr).
  409. Maybe()
  410. svc.EXPECT().
  411. RateParamsSubAddHandler(tc.input.snacOut).
  412. Maybe()
  413. svc.EXPECT().
  414. IdleNotificationHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
  415. Return(tc.handlerErr).
  416. Maybe()
  417. router := OServiceRouter{
  418. OServiceHandler: svc,
  419. }
  420. bufIn := &bytes.Buffer{}
  421. assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
  422. bufOut := &bytes.Buffer{}
  423. seq := uint32(1)
  424. err := router.RouteOService(Config{}, nil, nil, nil, nil, ChatRoom{}, tc.input.snacFrame, bufIn, bufOut, &seq)
  425. assert.ErrorIs(t, err, tc.expectErr)
  426. if tc.expectErr != nil {
  427. return
  428. }
  429. if tc.output == (XMessage{}) {
  430. // make sure no response was sent
  431. assert.Empty(t, bufOut.Bytes())
  432. return
  433. }
  434. // verify the FLAP frame
  435. flap := oscar.FlapFrame{}
  436. assert.NoError(t, oscar.Unmarshal(&flap, bufOut))
  437. // make sure the sequence number was incremented
  438. assert.Equal(t, uint32(2), seq)
  439. flapBuf, err := flap.SNACBuffer(bufOut)
  440. assert.NoError(t, err)
  441. // verify the SNAC frame
  442. snacFrame := oscar.SnacFrame{}
  443. assert.NoError(t, oscar.Unmarshal(&snacFrame, flapBuf))
  444. assert.Equal(t, tc.output.snacFrame, snacFrame)
  445. // verify the SNAC message
  446. snacBuf := &bytes.Buffer{}
  447. assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
  448. assert.Equal(t, snacBuf.Bytes(), flapBuf.Bytes())
  449. })
  450. }
  451. }