cmd_server_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. package toc
  2. import (
  3. "context"
  4. "log/slog"
  5. "net"
  6. "sync"
  7. "testing"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/mk6i/retro-aim-server/state"
  10. "github.com/mk6i/retro-aim-server/wire"
  11. )
  12. func TestOSCARProxy_RecvBOS_ChatIn(t *testing.T) {
  13. cases := []struct {
  14. // name is the unit test name
  15. name string
  16. // me is the TOC user session
  17. me *state.Session
  18. // chatID is the chat ID
  19. chatID int
  20. // givenMsg is the incoming SNAC
  21. givenMsg wire.SNACMessage
  22. // wantCmd is the expected TOC response
  23. wantCmd []byte
  24. }{
  25. {
  26. name: "send chat message",
  27. me: newTestSession("me"),
  28. chatID: 0,
  29. givenMsg: wire.SNACMessage{
  30. Body: wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
  31. TLVRestBlock: wire.TLVRestBlock{
  32. TLVList: wire.TLVList{
  33. wire.NewTLVBE(wire.ChatTLVSenderInformation, wire.TLVUserInfo{
  34. ScreenName: "them",
  35. }),
  36. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  37. TLVList: wire.TLVList{
  38. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "<p>hello world!</p>"),
  39. },
  40. }),
  41. },
  42. },
  43. },
  44. },
  45. wantCmd: []byte("CHAT_IN:0:them:F:<p>hello world!</p>"),
  46. },
  47. }
  48. for _, tc := range cases {
  49. t.Run(tc.name, func(t *testing.T) {
  50. ctx, cancel := context.WithCancel(context.Background())
  51. svc := OSCARProxy{
  52. Logger: slog.Default(),
  53. }
  54. ch := make(chan []byte)
  55. wg := &sync.WaitGroup{}
  56. wg.Add(1)
  57. go func() {
  58. defer wg.Done()
  59. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  60. }()
  61. status := tc.me.RelayMessage(tc.givenMsg)
  62. assert.Equal(t, state.SessSendOK, status)
  63. gotCmd := <-ch
  64. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  65. cancel()
  66. wg.Wait()
  67. })
  68. }
  69. }
  70. func TestOSCARProxy_RecvBOS_ChatUpdateBuddyArrived(t *testing.T) {
  71. cases := []struct {
  72. // name is the unit test name
  73. name string
  74. // me is the TOC user session
  75. me *state.Session
  76. // chatID is the chat ID
  77. chatID int
  78. // givenMsg is the incoming SNAC
  79. givenMsg wire.SNACMessage
  80. // wantCmd is the expected TOC response
  81. wantCmd []byte
  82. }{
  83. {
  84. name: "send chat participant arrival",
  85. me: newTestSession("me"),
  86. givenMsg: wire.SNACMessage{
  87. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
  88. Users: []wire.TLVUserInfo{
  89. {ScreenName: "user1"},
  90. {ScreenName: "user2"},
  91. },
  92. },
  93. },
  94. wantCmd: []byte("CHAT_UPDATE_BUDDY:0:T:user1:user2"),
  95. },
  96. }
  97. for _, tc := range cases {
  98. t.Run(tc.name, func(t *testing.T) {
  99. ctx, cancel := context.WithCancel(context.Background())
  100. svc := OSCARProxy{
  101. Logger: slog.Default(),
  102. }
  103. ch := make(chan []byte)
  104. wg := &sync.WaitGroup{}
  105. wg.Add(1)
  106. go func() {
  107. defer wg.Done()
  108. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  109. }()
  110. status := tc.me.RelayMessage(tc.givenMsg)
  111. assert.Equal(t, state.SessSendOK, status)
  112. gotCmd := <-ch
  113. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  114. cancel()
  115. wg.Wait()
  116. })
  117. }
  118. }
  119. func TestOSCARProxy_RecvBOS_ChatUpdateBuddyLeft(t *testing.T) {
  120. cases := []struct {
  121. // name is the unit test name
  122. name string
  123. // me is the TOC user session
  124. me *state.Session
  125. // chatID is the chat ID
  126. chatID int
  127. // givenMsg is the incoming SNAC
  128. givenMsg wire.SNACMessage
  129. // wantCmd is the expected TOC response
  130. wantCmd []byte
  131. }{
  132. {
  133. name: "send chat participant departure",
  134. me: newTestSession("me"),
  135. givenMsg: wire.SNACMessage{
  136. Body: wire.SNAC_0x0E_0x04_ChatUsersLeft{
  137. Users: []wire.TLVUserInfo{
  138. {ScreenName: "user1"},
  139. {ScreenName: "user2"},
  140. },
  141. },
  142. },
  143. wantCmd: []byte("CHAT_UPDATE_BUDDY:0:F:user1:user2"),
  144. },
  145. }
  146. for _, tc := range cases {
  147. t.Run(tc.name, func(t *testing.T) {
  148. ctx, cancel := context.WithCancel(context.Background())
  149. svc := OSCARProxy{
  150. Logger: slog.Default(),
  151. }
  152. ch := make(chan []byte)
  153. wg := &sync.WaitGroup{}
  154. wg.Add(1)
  155. go func() {
  156. defer wg.Done()
  157. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  158. }()
  159. status := tc.me.RelayMessage(tc.givenMsg)
  160. assert.Equal(t, state.SessSendOK, status)
  161. gotCmd := <-ch
  162. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  163. cancel()
  164. wg.Wait()
  165. })
  166. }
  167. }
  168. func TestOSCARProxy_RecvBOS_Eviled(t *testing.T) {
  169. cases := []struct {
  170. // name is the unit test name
  171. name string
  172. // me is the TOC user session
  173. me *state.Session
  174. // givenMsg is the incoming SNAC
  175. givenMsg wire.SNACMessage
  176. // chatRegistry is the chat registry for the current session
  177. chatRegistry *ChatRegistry
  178. // wantCmd is the expected TOC response
  179. wantCmd []byte
  180. }{
  181. {
  182. name: "anonymous warning - 10%",
  183. me: newTestSession("me"),
  184. givenMsg: wire.SNACMessage{
  185. Body: wire.SNAC_0x01_0x10_OServiceEvilNotification{
  186. NewEvil: 100,
  187. },
  188. },
  189. wantCmd: []byte("EVILED:10:"),
  190. },
  191. {
  192. name: "normal warning - 10%",
  193. me: newTestSession("me"),
  194. givenMsg: wire.SNACMessage{
  195. Body: wire.SNAC_0x01_0x10_OServiceEvilNotification{
  196. NewEvil: 100,
  197. Snitcher: &struct {
  198. wire.TLVUserInfo
  199. }{
  200. TLVUserInfo: wire.TLVUserInfo{
  201. ScreenName: "them",
  202. },
  203. },
  204. },
  205. },
  206. wantCmd: []byte("EVILED:10:them"),
  207. },
  208. }
  209. for _, tc := range cases {
  210. t.Run(tc.name, func(t *testing.T) {
  211. ctx, cancel := context.WithCancel(context.Background())
  212. svc := OSCARProxy{
  213. Logger: slog.Default(),
  214. }
  215. ch := make(chan []byte)
  216. wg := &sync.WaitGroup{}
  217. wg.Add(1)
  218. go func() {
  219. defer wg.Done()
  220. err := svc.RecvBOS(ctx, tc.me, tc.chatRegistry, ch)
  221. assert.NoError(t, err)
  222. }()
  223. status := tc.me.RelayMessage(tc.givenMsg)
  224. assert.Equal(t, state.SessSendOK, status)
  225. gotCmd := <-ch
  226. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  227. cancel()
  228. wg.Wait()
  229. })
  230. }
  231. }
  232. func TestOSCARProxy_RecvBOS_IMIn(t *testing.T) {
  233. cases := []struct {
  234. // name is the unit test name
  235. name string
  236. // me is the TOC user session
  237. me *state.Session
  238. // givenMsg is the incoming SNAC
  239. givenMsg wire.SNACMessage
  240. // chatRegistry is the chat registry for the current session
  241. chatRegistry *ChatRegistry
  242. // wantCmd is the expected TOC response
  243. wantCmd []byte
  244. }{
  245. {
  246. name: "send IM",
  247. me: newTestSession("me"),
  248. givenMsg: wire.SNACMessage{
  249. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  250. ChannelID: wire.ICBMChannelIM,
  251. TLVUserInfo: wire.TLVUserInfo{
  252. ScreenName: "them",
  253. },
  254. TLVRestBlock: wire.TLVRestBlock{
  255. TLVList: wire.TLVList{
  256. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  257. {
  258. ID: 0x5,
  259. Version: 0x1,
  260. Payload: []uint8{0x1, 0x1, 0x2},
  261. },
  262. {
  263. ID: 0x1,
  264. Version: 0x1,
  265. Payload: []uint8{
  266. 0x0, 0x0, // charset
  267. 0x0, 0x0, // lang
  268. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
  269. },
  270. },
  271. }),
  272. },
  273. },
  274. },
  275. },
  276. wantCmd: []byte("IM_IN:them:F:hello world!"),
  277. },
  278. {
  279. name: "send IM - auto-response",
  280. me: newTestSession("me"),
  281. givenMsg: wire.SNACMessage{
  282. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  283. ChannelID: wire.ICBMChannelIM,
  284. TLVUserInfo: wire.TLVUserInfo{
  285. ScreenName: "them",
  286. },
  287. TLVRestBlock: wire.TLVRestBlock{
  288. TLVList: wire.TLVList{
  289. wire.NewTLVBE(wire.ICBMTLVAutoResponse, []byte{}),
  290. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  291. {
  292. ID: 0x5,
  293. Version: 0x1,
  294. Payload: []uint8{0x1, 0x1, 0x2},
  295. },
  296. {
  297. ID: 0x1,
  298. Version: 0x1,
  299. Payload: []uint8{
  300. 0x0, 0x0, // charset
  301. 0x0, 0x0, // lang
  302. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
  303. },
  304. },
  305. }),
  306. },
  307. },
  308. },
  309. },
  310. wantCmd: []byte("IM_IN:them:T:hello world!"),
  311. },
  312. {
  313. name: "send chat invitation",
  314. me: newTestSession("me"),
  315. givenMsg: wire.SNACMessage{
  316. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  317. ChannelID: wire.ICBMChannelRendezvous,
  318. TLVUserInfo: wire.TLVUserInfo{
  319. ScreenName: "them",
  320. },
  321. TLVRestBlock: wire.TLVRestBlock{
  322. TLVList: wire.TLVList{
  323. wire.NewTLVBE(wire.ICBMTLVData, []wire.ICBMCh2Fragment{
  324. {
  325. Capability: wire.CapChat,
  326. TLVRestBlock: wire.TLVRestBlock{
  327. TLVList: wire.TLVList{
  328. wire.NewTLVBE(wire.ICBMRdvTLVTagsInvitation, "join my chat!"),
  329. wire.NewTLVBE(wire.ICBMRdvTLVTagsSvcData, wire.ICBMRoomInfo{
  330. Cookie: "a-b-the room",
  331. }),
  332. },
  333. },
  334. },
  335. }),
  336. },
  337. },
  338. },
  339. },
  340. chatRegistry: NewChatRegistry(),
  341. wantCmd: []byte("CHAT_INVITE:the room:0:them:join my chat!"),
  342. },
  343. {
  344. name: "receive file transfer rendezvous IM",
  345. me: newTestSession("me"),
  346. givenMsg: wire.SNACMessage{
  347. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  348. ChannelID: wire.ICBMChannelRendezvous,
  349. TLVUserInfo: newTestSession("them").TLVUserInfo(),
  350. TLVRestBlock: wire.TLVRestBlock{
  351. TLVList: wire.TLVList{
  352. wire.NewTLVBE(wire.ICBMTLVWantEvents, []byte{}),
  353. wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
  354. Cookie: [8]byte{'h', 'a', 'h', 'a', 'h', 'a', 'h', 'a'},
  355. Type: wire.ICBMRdvMessagePropose,
  356. Capability: wire.CapFileTransfer,
  357. TLVRestBlock: wire.TLVRestBlock{
  358. TLVList: wire.TLVList{
  359. wire.NewTLVBE(wire.ICBMRdvTLVTagsSeqNum, uint16(1)),
  360. wire.NewTLVBE(wire.ICBMRdvTLVTagsPort, uint16(4000)),
  361. wire.NewTLVBE(wire.ICBMRdvTLVTagsRdvIP, net.ParseIP("129.168.0.1").To4()),
  362. wire.NewTLVBE(wire.ICBMRdvTLVTagsRequesterIP, net.ParseIP("129.168.0.2").To4()),
  363. wire.NewTLVBE(wire.ICBMRdvTLVTagsVerifiedIP, net.ParseIP("129.168.0.3").To4()),
  364. wire.NewTLVBE(wire.ICBMRdvTLVTagsSvcData, []byte{'l', 'o', 'l'}),
  365. },
  366. },
  367. }),
  368. },
  369. },
  370. },
  371. },
  372. wantCmd: []byte("RVOUS_PROPOSE:them:09461343-4C7F-11D1-8222-444553540000:aGFoYWhhaGE=:1:129.168.0.1:129.168.0.2:129.168.0.3:4000:10001:bG9s"),
  373. },
  374. }
  375. for _, tc := range cases {
  376. t.Run(tc.name, func(t *testing.T) {
  377. ctx, cancel := context.WithCancel(context.Background())
  378. svc := OSCARProxy{
  379. Logger: slog.Default(),
  380. }
  381. ch := make(chan []byte)
  382. wg := &sync.WaitGroup{}
  383. wg.Add(1)
  384. go func() {
  385. defer wg.Done()
  386. err := svc.RecvBOS(ctx, tc.me, tc.chatRegistry, ch)
  387. assert.NoError(t, err)
  388. }()
  389. status := tc.me.RelayMessage(tc.givenMsg)
  390. assert.Equal(t, state.SessSendOK, status)
  391. gotCmd := <-ch
  392. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  393. cancel()
  394. wg.Wait()
  395. })
  396. }
  397. }
  398. func TestOSCARProxy_RecvBOS_UpdateBuddyArrival(t *testing.T) {
  399. cases := []struct {
  400. // name is the unit test name
  401. name string
  402. // me is the TOC user session
  403. me *state.Session
  404. // givenMsg is the incoming SNAC
  405. givenMsg wire.SNACMessage
  406. // wantCmd is the expected TOC response
  407. wantCmd []byte
  408. }{
  409. {
  410. name: "send buddy arrival - buddy online",
  411. me: newTestSession("me"),
  412. givenMsg: wire.SNACMessage{
  413. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  414. TLVUserInfo: wire.TLVUserInfo{
  415. ScreenName: "me",
  416. WarningLevel: 0,
  417. TLVBlock: wire.TLVBlock{
  418. TLVList: wire.TLVList{
  419. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  420. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  421. },
  422. },
  423. },
  424. },
  425. },
  426. wantCmd: []byte("UPDATE_BUDDY:me:T:0:1234:5678: O "),
  427. },
  428. {
  429. name: "send buddy arrival - buddy warned 10%",
  430. me: newTestSession("me"),
  431. givenMsg: wire.SNACMessage{
  432. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  433. TLVUserInfo: wire.TLVUserInfo{
  434. ScreenName: "me",
  435. WarningLevel: 100,
  436. TLVBlock: wire.TLVBlock{
  437. TLVList: wire.TLVList{
  438. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  439. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  440. },
  441. },
  442. },
  443. },
  444. },
  445. wantCmd: []byte("UPDATE_BUDDY:me:T:10:1234:5678: O "),
  446. },
  447. {
  448. name: "send buddy arrival - buddy away",
  449. me: newTestSession("me"),
  450. givenMsg: wire.SNACMessage{
  451. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  452. TLVUserInfo: wire.TLVUserInfo{
  453. ScreenName: "me",
  454. WarningLevel: 0,
  455. TLVBlock: wire.TLVBlock{
  456. TLVList: wire.TLVList{
  457. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  458. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  459. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagUnavailable),
  460. },
  461. },
  462. },
  463. },
  464. },
  465. wantCmd: []byte("UPDATE_BUDDY:me:T:0:1234:5678: OU"),
  466. },
  467. }
  468. for _, tc := range cases {
  469. t.Run(tc.name, func(t *testing.T) {
  470. ctx, cancel := context.WithCancel(context.Background())
  471. svc := OSCARProxy{
  472. Logger: slog.Default(),
  473. }
  474. ch := make(chan []byte)
  475. wg := &sync.WaitGroup{}
  476. wg.Add(1)
  477. go func() {
  478. defer wg.Done()
  479. err := svc.RecvBOS(ctx, tc.me, nil, ch)
  480. assert.NoError(t, err)
  481. }()
  482. status := tc.me.RelayMessage(tc.givenMsg)
  483. assert.Equal(t, state.SessSendOK, status)
  484. gotCmd := <-ch
  485. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  486. cancel()
  487. wg.Wait()
  488. })
  489. }
  490. }
  491. func TestOSCARProxy_RecvBOS_UpdateBuddyDeparted(t *testing.T) {
  492. cases := []struct {
  493. // name is the unit test name
  494. name string
  495. // me is the TOC user session
  496. me *state.Session
  497. // givenMsg is the incoming SNAC
  498. givenMsg wire.SNACMessage
  499. // wantCmd is the expected TOC response
  500. wantCmd []byte
  501. }{
  502. {
  503. name: "send buddy departure",
  504. me: newTestSession("me"),
  505. givenMsg: wire.SNACMessage{
  506. Body: wire.SNAC_0x03_0x0C_BuddyDeparted{
  507. TLVUserInfo: wire.TLVUserInfo{
  508. ScreenName: "me",
  509. },
  510. },
  511. },
  512. wantCmd: []byte("UPDATE_BUDDY:me:F:0:0:0: "),
  513. },
  514. }
  515. for _, tc := range cases {
  516. t.Run(tc.name, func(t *testing.T) {
  517. ctx, cancel := context.WithCancel(context.Background())
  518. svc := OSCARProxy{
  519. Logger: slog.Default(),
  520. }
  521. ch := make(chan []byte)
  522. wg := &sync.WaitGroup{}
  523. wg.Add(1)
  524. go func() {
  525. defer wg.Done()
  526. err := svc.RecvBOS(ctx, tc.me, nil, ch)
  527. assert.NoError(t, err)
  528. }()
  529. status := tc.me.RelayMessage(tc.givenMsg)
  530. assert.Equal(t, state.SessSendOK, status)
  531. gotCmd := <-ch
  532. assert.Equal(t, string(tc.wantCmd), string(gotCmd))
  533. cancel()
  534. wg.Wait()
  535. })
  536. }
  537. }
  538. func TestOSCARProxy_RecvBOS_Signout(t *testing.T) {
  539. }