cmd_server_test.go 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313
  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/stretchr/testify/mock"
  10. "github.com/mk6i/open-oscar-server/state"
  11. "github.com/mk6i/open-oscar-server/wire"
  12. )
  13. func TestOSCARProxy_RecvBOS_ChatIn(t *testing.T) {
  14. cases := []struct {
  15. // name is the unit test name
  16. name string
  17. // me is the TOC user session
  18. me *state.SessionInstance
  19. // chatID is the chat ID
  20. chatID int
  21. // givenMsg is the incoming SNAC
  22. givenMsg wire.SNACMessage
  23. // wantCmd is the expected TOC response
  24. wantCmd string
  25. }{
  26. {
  27. name: "send chat message - plain CHAT_IN",
  28. me: newTestSession("me"),
  29. chatID: 0,
  30. givenMsg: wire.SNACMessage{
  31. Body: wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
  32. TLVRestBlock: wire.TLVRestBlock{
  33. TLVList: wire.TLVList{
  34. wire.NewTLVBE(wire.ChatTLVSenderInformation, wire.TLVUserInfo{
  35. ScreenName: "them",
  36. }),
  37. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  38. TLVList: wire.TLVList{
  39. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "<p>hello world!</p>"),
  40. },
  41. }),
  42. },
  43. },
  44. },
  45. },
  46. wantCmd: "CHAT_IN:0:them:F:<p>hello world!</p>",
  47. },
  48. {
  49. name: "send chat message - TOC2 encoded CHAT_IN_ENC",
  50. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) }),
  51. chatID: 0,
  52. givenMsg: wire.SNACMessage{
  53. Body: wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
  54. TLVRestBlock: wire.TLVRestBlock{
  55. TLVList: wire.TLVList{
  56. wire.NewTLVBE(wire.ChatTLVSenderInformation, wire.TLVUserInfo{
  57. ScreenName: "them",
  58. }),
  59. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  60. TLVList: wire.TLVList{
  61. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "<p>hello world!</p>"),
  62. },
  63. }),
  64. },
  65. },
  66. },
  67. },
  68. wantCmd: "CHAT_IN_ENC:0:them:F:A:en:<p>hello world!</p>",
  69. },
  70. }
  71. for _, tc := range cases {
  72. t.Run(tc.name, func(t *testing.T) {
  73. ctx, cancel := context.WithCancel(context.Background())
  74. svc := OSCARProxy{
  75. Logger: slog.Default(),
  76. }
  77. ch := make(chan []string)
  78. wg := &sync.WaitGroup{}
  79. wg.Add(1)
  80. go func() {
  81. defer wg.Done()
  82. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  83. }()
  84. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  85. assert.Equal(t, state.SessSendOK, status)
  86. gotCmd := <-ch
  87. assert.Equal(t, tc.wantCmd, gotCmd[0])
  88. cancel()
  89. wg.Wait()
  90. })
  91. }
  92. }
  93. func TestOSCARProxy_RecvBOS_ChatUpdateBuddyArrived(t *testing.T) {
  94. cases := []struct {
  95. // name is the unit test name
  96. name string
  97. // me is the TOC user session
  98. me *state.SessionInstance
  99. // chatID is the chat ID
  100. chatID int
  101. // givenMsg is the incoming SNAC
  102. givenMsg wire.SNACMessage
  103. // wantCmd is the expected TOC response
  104. wantCmd []string
  105. }{
  106. {
  107. name: "send chat participant arrival",
  108. me: newTestSession("me"),
  109. givenMsg: wire.SNACMessage{
  110. Body: wire.SNAC_0x0E_0x03_ChatUsersJoined{
  111. Users: []wire.TLVUserInfo{
  112. {ScreenName: "user1"},
  113. {ScreenName: "user2"},
  114. },
  115. },
  116. },
  117. wantCmd: []string{"CHAT_UPDATE_BUDDY:0:T:user1:user2"},
  118. },
  119. }
  120. for _, tc := range cases {
  121. t.Run(tc.name, func(t *testing.T) {
  122. ctx, cancel := context.WithCancel(context.Background())
  123. svc := OSCARProxy{
  124. Logger: slog.Default(),
  125. }
  126. ch := make(chan []string)
  127. wg := &sync.WaitGroup{}
  128. wg.Add(1)
  129. go func() {
  130. defer wg.Done()
  131. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  132. }()
  133. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  134. assert.Equal(t, state.SessSendOK, status)
  135. gotCmd := <-ch
  136. assert.Equal(t, tc.wantCmd[0], gotCmd[0])
  137. cancel()
  138. wg.Wait()
  139. })
  140. }
  141. }
  142. func TestOSCARProxy_RecvBOS_ChatUpdateBuddyLeft(t *testing.T) {
  143. cases := []struct {
  144. // name is the unit test name
  145. name string
  146. // me is the TOC user session
  147. me *state.SessionInstance
  148. // chatID is the chat ID
  149. chatID int
  150. // givenMsg is the incoming SNAC
  151. givenMsg wire.SNACMessage
  152. // wantCmd is the expected TOC response
  153. wantCmd []string
  154. }{
  155. {
  156. name: "send chat participant departure",
  157. me: newTestSession("me"),
  158. givenMsg: wire.SNACMessage{
  159. Body: wire.SNAC_0x0E_0x04_ChatUsersLeft{
  160. Users: []wire.TLVUserInfo{
  161. {ScreenName: "user1"},
  162. {ScreenName: "user2"},
  163. },
  164. },
  165. },
  166. wantCmd: []string{"CHAT_UPDATE_BUDDY:0:F:user1:user2"},
  167. },
  168. }
  169. for _, tc := range cases {
  170. t.Run(tc.name, func(t *testing.T) {
  171. ctx, cancel := context.WithCancel(context.Background())
  172. svc := OSCARProxy{
  173. Logger: slog.Default(),
  174. }
  175. ch := make(chan []string)
  176. wg := &sync.WaitGroup{}
  177. wg.Add(1)
  178. go func() {
  179. defer wg.Done()
  180. svc.RecvChat(ctx, tc.me, tc.chatID, ch)
  181. }()
  182. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  183. assert.Equal(t, state.SessSendOK, status)
  184. gotCmd := <-ch
  185. assert.Equal(t, tc.wantCmd[0], gotCmd[0])
  186. cancel()
  187. wg.Wait()
  188. })
  189. }
  190. }
  191. func TestOSCARProxy_RecvBOS_Eviled(t *testing.T) {
  192. cases := []struct {
  193. // name is the unit test name
  194. name string
  195. // me is the TOC user session
  196. me *state.SessionInstance
  197. // givenMsg is the incoming SNAC
  198. givenMsg wire.SNACMessage
  199. // chatRegistry is the chat registry for the current session
  200. chatRegistry *ChatRegistry
  201. // wantCmd is the expected TOC response
  202. wantCmd []string
  203. }{
  204. {
  205. name: "anonymous warning - 10%",
  206. me: newTestSession("me"),
  207. givenMsg: wire.SNACMessage{
  208. Body: wire.SNAC_0x01_0x10_OServiceEvilNotification{
  209. NewEvil: 100,
  210. },
  211. },
  212. wantCmd: []string{"EVILED:10:"},
  213. },
  214. {
  215. name: "normal warning - 10%",
  216. me: newTestSession("me"),
  217. givenMsg: wire.SNACMessage{
  218. Body: wire.SNAC_0x01_0x10_OServiceEvilNotification{
  219. NewEvil: 100,
  220. Snitcher: &struct {
  221. wire.TLVUserInfo
  222. }{
  223. TLVUserInfo: wire.TLVUserInfo{
  224. ScreenName: "them",
  225. },
  226. },
  227. },
  228. },
  229. wantCmd: []string{"EVILED:10:them"},
  230. },
  231. }
  232. for _, tc := range cases {
  233. t.Run(tc.name, func(t *testing.T) {
  234. ctx, cancel := context.WithCancel(context.Background())
  235. svc := testOSCARProxy(t)
  236. ch := make(chan []string)
  237. wg := &sync.WaitGroup{}
  238. wg.Add(1)
  239. go func() {
  240. defer wg.Done()
  241. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  242. assert.NoError(t, err)
  243. }()
  244. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  245. assert.Equal(t, state.SessSendOK, status)
  246. gotCmd := <-ch
  247. assert.Equal(t, tc.wantCmd[0], gotCmd[0])
  248. cancel()
  249. wg.Wait()
  250. })
  251. }
  252. }
  253. func TestOSCARProxy_RecvBOS_IMIn(t *testing.T) {
  254. cases := []struct {
  255. // name is the unit test name
  256. name string
  257. // me is the TOC user session
  258. me *state.SessionInstance
  259. // givenMsg is the incoming SNAC
  260. givenMsg wire.SNACMessage
  261. // wantCmd is the expected TOC response
  262. wantCmd []string
  263. }{
  264. {
  265. name: "send IM",
  266. me: newTestSession("me"),
  267. givenMsg: wire.SNACMessage{
  268. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  269. ChannelID: wire.ICBMChannelIM,
  270. TLVUserInfo: wire.TLVUserInfo{
  271. ScreenName: "them",
  272. },
  273. TLVRestBlock: wire.TLVRestBlock{
  274. TLVList: wire.TLVList{
  275. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  276. {
  277. ID: 0x5,
  278. Version: 0x1,
  279. Payload: []uint8{0x1, 0x1, 0x2},
  280. },
  281. {
  282. ID: 0x1,
  283. Version: 0x1,
  284. Payload: []uint8{
  285. 0x0, 0x0, // charset
  286. 0x0, 0x0, // lang
  287. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
  288. },
  289. },
  290. }),
  291. },
  292. },
  293. },
  294. },
  295. wantCmd: []string{"IM_IN:them:F:hello world!"},
  296. },
  297. {
  298. name: "send IM - auto-response",
  299. me: newTestSession("me"),
  300. givenMsg: wire.SNACMessage{
  301. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  302. ChannelID: wire.ICBMChannelIM,
  303. TLVUserInfo: wire.TLVUserInfo{
  304. ScreenName: "them",
  305. },
  306. TLVRestBlock: wire.TLVRestBlock{
  307. TLVList: wire.TLVList{
  308. wire.NewTLVBE(wire.ICBMTLVAutoResponse, []byte{}),
  309. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  310. {
  311. ID: 0x5,
  312. Version: 0x1,
  313. Payload: []uint8{0x1, 0x1, 0x2},
  314. },
  315. {
  316. ID: 0x1,
  317. Version: 0x1,
  318. Payload: []uint8{
  319. 0x0, 0x0, // charset
  320. 0x0, 0x0, // lang
  321. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
  322. },
  323. },
  324. }),
  325. },
  326. },
  327. },
  328. },
  329. wantCmd: []string{"IM_IN:them:T:hello world!"},
  330. },
  331. {
  332. name: "send IM - TOC2 (IM_IN2)",
  333. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  334. givenMsg: wire.SNACMessage{
  335. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  336. ChannelID: wire.ICBMChannelIM,
  337. TLVUserInfo: wire.TLVUserInfo{
  338. ScreenName: "them",
  339. },
  340. TLVRestBlock: wire.TLVRestBlock{
  341. TLVList: wire.TLVList{
  342. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  343. {ID: 0x5, Version: 0x1, Payload: []uint8{0x1, 0x1, 0x2}},
  344. {ID: 0x1, Version: 0x1, Payload: []uint8{0x0, 0x0, 0x0, 0x0, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'}},
  345. }),
  346. },
  347. },
  348. },
  349. },
  350. wantCmd: []string{"IM_IN2:them:F:F:hello world!"},
  351. },
  352. {
  353. name: "send IM - TOC2 with encoded messaging (IM_IN_ENC2)",
  354. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) }),
  355. givenMsg: wire.SNACMessage{
  356. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  357. ChannelID: wire.ICBMChannelIM,
  358. TLVUserInfo: wire.TLVUserInfo{
  359. ScreenName: "them",
  360. TLVBlock: wire.TLVBlock{
  361. TLVList: wire.TLVList{
  362. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  363. },
  364. },
  365. },
  366. TLVRestBlock: wire.TLVRestBlock{
  367. TLVList: wire.TLVList{
  368. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  369. {ID: 0x5, Version: 0x1, Payload: []uint8{0x1, 0x1, 0x2}},
  370. {ID: 0x1, Version: 0x1, Payload: []uint8{0x0, 0x0, 0x0, 0x0, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'}},
  371. }),
  372. },
  373. },
  374. },
  375. },
  376. wantCmd: []string{"IM_IN_ENC2:them:F:F:T: O :F:L:en:hello world!"},
  377. },
  378. {
  379. name: "send IM - TOC2 encoded messaging missing OServiceUserInfoUserFlags returns empty",
  380. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) }),
  381. givenMsg: wire.SNACMessage{
  382. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  383. ChannelID: wire.ICBMChannelIM,
  384. TLVUserInfo: wire.TLVUserInfo{
  385. ScreenName: "them",
  386. // no TLVList / OServiceUserInfoUserFlags
  387. },
  388. TLVRestBlock: wire.TLVRestBlock{
  389. TLVList: wire.TLVList{
  390. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  391. {ID: 0x5, Version: 0x1, Payload: []uint8{0x1, 0x1, 0x2}},
  392. {ID: 0x1, Version: 0x1, Payload: []uint8{0x0, 0x0, 0x0, 0x0, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'}},
  393. }),
  394. },
  395. },
  396. },
  397. },
  398. wantCmd: []string{"IM_IN_ENC2:them:F:F:T: :F:L:en:hello world!"},
  399. },
  400. {
  401. name: "send chat invitation",
  402. me: newTestSession("me"),
  403. givenMsg: wire.SNACMessage{
  404. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  405. ChannelID: wire.ICBMChannelRendezvous,
  406. TLVUserInfo: wire.TLVUserInfo{
  407. ScreenName: "them",
  408. },
  409. TLVRestBlock: wire.TLVRestBlock{
  410. TLVList: wire.TLVList{
  411. wire.NewTLVBE(wire.ICBMTLVData, []wire.ICBMCh2Fragment{
  412. {
  413. Capability: wire.CapChat,
  414. TLVRestBlock: wire.TLVRestBlock{
  415. TLVList: wire.TLVList{
  416. wire.NewTLVBE(wire.ICBMRdvTLVTagsInvitation, "join my chat!"),
  417. wire.NewTLVBE(wire.ICBMRdvTLVTagsSvcData, wire.ICBMRoomInfo{
  418. Cookie: "a-b-the room",
  419. }),
  420. },
  421. },
  422. },
  423. }),
  424. },
  425. },
  426. },
  427. },
  428. wantCmd: []string{"CHAT_INVITE:the room:0:them:join my chat!"},
  429. },
  430. {
  431. name: "receive file transfer rendezvous IM",
  432. me: newTestSession("me"),
  433. givenMsg: wire.SNACMessage{
  434. Body: wire.SNAC_0x04_0x07_ICBMChannelMsgToClient{
  435. ChannelID: wire.ICBMChannelRendezvous,
  436. TLVUserInfo: newTestSession("them").Session().TLVUserInfo(),
  437. TLVRestBlock: wire.TLVRestBlock{
  438. TLVList: wire.TLVList{
  439. wire.NewTLVBE(wire.ICBMTLVWantEvents, []byte{}),
  440. wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
  441. Cookie: [8]byte{'h', 'a', 'h', 'a', 'h', 'a', 'h', 'a'},
  442. Type: wire.ICBMRdvMessagePropose,
  443. Capability: wire.CapFileTransfer,
  444. TLVRestBlock: wire.TLVRestBlock{
  445. TLVList: wire.TLVList{
  446. wire.NewTLVBE(wire.ICBMRdvTLVTagsSeqNum, uint16(1)),
  447. wire.NewTLVBE(wire.ICBMRdvTLVTagsPort, uint16(4000)),
  448. wire.NewTLVBE(wire.ICBMRdvTLVTagsRdvIP, net.ParseIP("129.168.0.1").To4()),
  449. wire.NewTLVBE(wire.ICBMRdvTLVTagsRequesterIP, net.ParseIP("129.168.0.2").To4()),
  450. wire.NewTLVBE(wire.ICBMRdvTLVTagsVerifiedIP, net.ParseIP("129.168.0.3").To4()),
  451. wire.NewTLVBE(wire.ICBMRdvTLVTagsSvcData, []byte{'l', 'o', 'l'}),
  452. },
  453. },
  454. }),
  455. },
  456. },
  457. },
  458. },
  459. wantCmd: []string{"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"},
  460. },
  461. }
  462. for _, tc := range cases {
  463. t.Run(tc.name, func(t *testing.T) {
  464. ctx, cancel := context.WithCancel(context.Background())
  465. svc := testOSCARProxy(t)
  466. ch := make(chan []string)
  467. wg := &sync.WaitGroup{}
  468. wg.Add(1)
  469. go func() {
  470. defer wg.Done()
  471. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  472. assert.NoError(t, err)
  473. }()
  474. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  475. assert.Equal(t, state.SessSendOK, status)
  476. gotCmd := <-ch
  477. assert.Equal(t, tc.wantCmd[0], gotCmd[0])
  478. cancel()
  479. wg.Wait()
  480. })
  481. }
  482. }
  483. func TestOSCARProxy_RecvBOS_UpdateBuddyArrival(t *testing.T) {
  484. cases := []struct {
  485. // name is the unit test name
  486. name string
  487. // me is the TOC user session
  488. me *state.SessionInstance
  489. // givenMsg is the incoming SNAC
  490. givenMsg wire.SNACMessage
  491. // wantCmd is the expected TOC response
  492. wantCmd []string
  493. }{
  494. {
  495. name: "send buddy arrival - buddy online",
  496. me: newTestSession("me"),
  497. givenMsg: wire.SNACMessage{
  498. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  499. TLVUserInfo: wire.TLVUserInfo{
  500. ScreenName: "me",
  501. WarningLevel: 0,
  502. TLVBlock: wire.TLVBlock{
  503. TLVList: wire.TLVList{
  504. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  505. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  506. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  507. },
  508. },
  509. },
  510. },
  511. },
  512. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678: O "},
  513. },
  514. {
  515. name: "send buddy arrival - buddy warned 10%",
  516. me: newTestSession("me"),
  517. givenMsg: wire.SNACMessage{
  518. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  519. TLVUserInfo: wire.TLVUserInfo{
  520. ScreenName: "me",
  521. WarningLevel: 100,
  522. TLVBlock: wire.TLVBlock{
  523. TLVList: wire.TLVList{
  524. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  525. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  526. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  527. },
  528. },
  529. },
  530. },
  531. },
  532. wantCmd: []string{"UPDATE_BUDDY:me:T:10:1234:5678: O "},
  533. },
  534. {
  535. name: "send buddy arrival - buddy away",
  536. me: newTestSession("me"),
  537. givenMsg: wire.SNACMessage{
  538. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  539. TLVUserInfo: wire.TLVUserInfo{
  540. ScreenName: "me",
  541. WarningLevel: 0,
  542. TLVBlock: wire.TLVBlock{
  543. TLVList: wire.TLVList{
  544. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  545. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  546. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree|wire.OServiceUserFlagUnavailable),
  547. },
  548. },
  549. },
  550. },
  551. },
  552. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678: OU"},
  553. },
  554. {
  555. name: "send buddy arrival - user class AOL (userClassString uc[0])",
  556. me: newTestSession("me"),
  557. givenMsg: wire.SNACMessage{
  558. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  559. TLVUserInfo: wire.TLVUserInfo{
  560. ScreenName: "me",
  561. WarningLevel: 0,
  562. TLVBlock: wire.TLVBlock{
  563. TLVList: wire.TLVList{
  564. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  565. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  566. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagAOL),
  567. },
  568. },
  569. },
  570. },
  571. },
  572. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678:A "},
  573. },
  574. {
  575. name: "send buddy arrival - user class Administrator (userClassString uc[1])",
  576. me: newTestSession("me"),
  577. givenMsg: wire.SNACMessage{
  578. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  579. TLVUserInfo: wire.TLVUserInfo{
  580. ScreenName: "me",
  581. WarningLevel: 0,
  582. TLVBlock: wire.TLVBlock{
  583. TLVList: wire.TLVList{
  584. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  585. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  586. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagAdministrator),
  587. },
  588. },
  589. },
  590. },
  591. },
  592. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678: A "},
  593. },
  594. {
  595. name: "send buddy arrival - user class Wireless (userClassString uc[1])",
  596. me: newTestSession("me"),
  597. givenMsg: wire.SNACMessage{
  598. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  599. TLVUserInfo: wire.TLVUserInfo{
  600. ScreenName: "me",
  601. WarningLevel: 0,
  602. TLVBlock: wire.TLVBlock{
  603. TLVList: wire.TLVList{
  604. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  605. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  606. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagWireless),
  607. },
  608. },
  609. },
  610. },
  611. },
  612. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678: C "},
  613. },
  614. {
  615. name: "send buddy arrival - user class Unconfirmed (userClassString uc[1])",
  616. me: newTestSession("me"),
  617. givenMsg: wire.SNACMessage{
  618. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  619. TLVUserInfo: wire.TLVUserInfo{
  620. ScreenName: "me",
  621. WarningLevel: 0,
  622. TLVBlock: wire.TLVBlock{
  623. TLVList: wire.TLVList{
  624. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  625. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  626. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagUnconfirmed),
  627. },
  628. },
  629. },
  630. },
  631. },
  632. wantCmd: []string{"UPDATE_BUDDY:me:T:0:1234:5678: U "},
  633. },
  634. {
  635. name: "send buddy arrival - TOC2 no caps (userInfoToBuddyCaps returns empty when no OServiceUserInfoOscarCaps TLV)",
  636. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  637. givenMsg: wire.SNACMessage{
  638. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  639. TLVUserInfo: wire.TLVUserInfo{
  640. ScreenName: "buddy",
  641. WarningLevel: 0,
  642. TLVBlock: wire.TLVBlock{
  643. TLVList: wire.TLVList{
  644. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  645. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  646. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  647. },
  648. },
  649. },
  650. },
  651. },
  652. wantCmd: []string{"UPDATE_BUDDY2:buddy:T:0:1234:5678: O :"},
  653. },
  654. {
  655. name: "send buddy arrival - TOC2 caps length not divisible by 16 (userInfoToBuddyCaps returns empty)",
  656. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  657. givenMsg: wire.SNACMessage{
  658. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  659. TLVUserInfo: wire.TLVUserInfo{
  660. ScreenName: "buddy",
  661. WarningLevel: 0,
  662. TLVBlock: wire.TLVBlock{
  663. TLVList: wire.TLVList{
  664. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  665. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  666. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  667. // Invalid: 8 bytes, not divisible by 16
  668. wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, []byte{0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4}),
  669. },
  670. },
  671. },
  672. },
  673. },
  674. wantCmd: []string{"UPDATE_BUDDY2:buddy:T:0:1234:5678: O :"},
  675. },
  676. {
  677. name: "send buddy arrival - TOC2 with one capability (userInfoToBuddyCaps formats caps as UUIDs)",
  678. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  679. givenMsg: wire.SNACMessage{
  680. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  681. TLVUserInfo: wire.TLVUserInfo{
  682. ScreenName: "buddy",
  683. WarningLevel: 0,
  684. TLVBlock: wire.TLVBlock{
  685. TLVList: wire.TLVList{
  686. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  687. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  688. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  689. // One 16-byte cap: UUID 550e8400-e29b-41d4-a716-446655440000
  690. wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, []byte{
  691. 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4,
  692. 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44, 0x00, 0x00,
  693. }),
  694. },
  695. },
  696. },
  697. },
  698. },
  699. wantCmd: []string{"UPDATE_BUDDY2:buddy:T:0:1234:5678: O :", "BUDDY_CAPS2:buddy:550e8400-e29b-41d4-a716-446655440000"},
  700. },
  701. {
  702. name: "send buddy arrival - TOC2 with two capabilities",
  703. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  704. givenMsg: wire.SNACMessage{
  705. Body: wire.SNAC_0x03_0x0B_BuddyArrived{
  706. TLVUserInfo: wire.TLVUserInfo{
  707. ScreenName: "buddy",
  708. WarningLevel: 0,
  709. TLVBlock: wire.TLVBlock{
  710. TLVList: wire.TLVList{
  711. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  712. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  713. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  714. // Two 16-byte caps
  715. wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, []byte{
  716. 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44, 0x00, 0x00,
  717. 0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00,
  718. }),
  719. },
  720. },
  721. },
  722. },
  723. },
  724. wantCmd: []string{"UPDATE_BUDDY2:buddy:T:0:1234:5678: O :", "BUDDY_CAPS2:buddy:550e8400-e29b-41d4-a716-446655440000,748f2420-6287-11d1-8222-444553540000"},
  725. },
  726. }
  727. for _, tc := range cases {
  728. t.Run(tc.name, func(t *testing.T) {
  729. ctx, cancel := context.WithCancel(context.Background())
  730. svc := testOSCARProxy(t)
  731. ch := make(chan []string)
  732. wg := &sync.WaitGroup{}
  733. wg.Add(1)
  734. go func() {
  735. defer wg.Done()
  736. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  737. assert.NoError(t, err)
  738. }()
  739. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  740. assert.Equal(t, state.SessSendOK, status)
  741. gotCmd := <-ch
  742. assert.Len(t, gotCmd, len(tc.wantCmd))
  743. for i, want := range tc.wantCmd {
  744. assert.Equal(t, want, gotCmd[i])
  745. }
  746. cancel()
  747. wg.Wait()
  748. })
  749. }
  750. }
  751. func TestOSCARProxy_RecvBOS_UpdateBuddyDeparted(t *testing.T) {
  752. cases := []struct {
  753. // name is the unit test name
  754. name string
  755. // me is the TOC user session
  756. me *state.SessionInstance
  757. // givenMsg is the incoming SNAC
  758. givenMsg wire.SNACMessage
  759. // wantCmd is the expected TOC response
  760. wantCmd []string
  761. }{
  762. {
  763. name: "send buddy departure TOC1",
  764. me: newTestSession("me"),
  765. givenMsg: wire.SNACMessage{
  766. Body: wire.SNAC_0x03_0x0C_BuddyDeparted{
  767. TLVUserInfo: wire.TLVUserInfo{
  768. ScreenName: "me",
  769. },
  770. },
  771. },
  772. wantCmd: []string{"UPDATE_BUDDY:me:F:0:0:0: "},
  773. },
  774. {
  775. name: "send buddy departure TOC2",
  776. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) }),
  777. givenMsg: wire.SNACMessage{
  778. Body: wire.SNAC_0x03_0x0C_BuddyDeparted{
  779. TLVUserInfo: wire.TLVUserInfo{
  780. ScreenName: "me",
  781. },
  782. },
  783. },
  784. wantCmd: []string{"UPDATE_BUDDY2:me:F:0:0:0: :"},
  785. },
  786. }
  787. for _, tc := range cases {
  788. t.Run(tc.name, func(t *testing.T) {
  789. ctx, cancel := context.WithCancel(context.Background())
  790. svc := testOSCARProxy(t)
  791. ch := make(chan []string)
  792. wg := &sync.WaitGroup{}
  793. wg.Add(1)
  794. go func() {
  795. defer wg.Done()
  796. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  797. assert.NoError(t, err)
  798. }()
  799. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  800. assert.Equal(t, state.SessSendOK, status)
  801. gotCmd := <-ch
  802. assert.Equal(t, tc.wantCmd[0], gotCmd[0])
  803. cancel()
  804. wg.Wait()
  805. })
  806. }
  807. }
  808. func TestOSCARProxy_RecvBOS_ClientEvent(t *testing.T) {
  809. cases := []struct {
  810. name string
  811. me *state.SessionInstance
  812. givenMsg wire.SNACMessage
  813. wantCmd []string
  814. }{
  815. {
  816. name: "TOC2 client receives CLIENT_EVENT2 (typing event)",
  817. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  818. givenMsg: wire.SNACMessage{
  819. Body: wire.SNAC_0x04_0x14_ICBMClientEvent{
  820. ScreenName: "buddy",
  821. Event: 1, // typing
  822. },
  823. },
  824. wantCmd: []string{"CLIENT_EVENT2:buddy:1"},
  825. },
  826. {
  827. name: "TOC2 client receives CLIENT_EVENT2 event 0 (idle)",
  828. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  829. givenMsg: wire.SNACMessage{
  830. Body: wire.SNAC_0x04_0x14_ICBMClientEvent{
  831. ScreenName: "alice",
  832. Event: 0,
  833. },
  834. },
  835. wantCmd: []string{"CLIENT_EVENT2:alice:0"},
  836. },
  837. {
  838. name: "TOC2 client receives CLIENT_EVENT2 event 2 (entered text)",
  839. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(false) }),
  840. givenMsg: wire.SNACMessage{
  841. Body: wire.SNAC_0x04_0x14_ICBMClientEvent{
  842. ScreenName: "bob",
  843. Event: 2,
  844. },
  845. },
  846. wantCmd: []string{"CLIENT_EVENT2:bob:2"},
  847. },
  848. }
  849. for _, tc := range cases {
  850. t.Run(tc.name, func(t *testing.T) {
  851. ctx, cancel := context.WithCancel(context.Background())
  852. svc := testOSCARProxy(t)
  853. ch := make(chan []string)
  854. wg := &sync.WaitGroup{}
  855. wg.Add(1)
  856. go func() {
  857. defer wg.Done()
  858. err := svc.RecvBOS(ctx, tc.me, NewChatRegistry(), ch)
  859. assert.NoError(t, err)
  860. }()
  861. status := tc.me.RelayMessageToInstance(tc.givenMsg)
  862. assert.Equal(t, state.SessSendOK, status)
  863. gotCmd := <-ch
  864. assert.Equal(t, tc.wantCmd, gotCmd)
  865. cancel()
  866. wg.Wait()
  867. })
  868. }
  869. }
  870. func TestOSCARProxy_RecvBOS_Signout(t *testing.T) {
  871. }
  872. func TestOSCARProxy_RecvBOS_Inserted2(t *testing.T) {
  873. buddyWithAlias := wire.FeedbagItem{
  874. ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice",
  875. TLVLBlock: wire.TLVLBlock{},
  876. }
  877. buddyWithAlias.Append(wire.NewTLVBE(wire.FeedbagAttributesAlias, []byte("Alice N.")))
  878. cases := []struct {
  879. name string
  880. snac wire.SNAC_0x13_0x09_FeedbagUpdateItem
  881. wantCmd []string
  882. mockParams mockParams
  883. }{
  884. {
  885. name: "one buddy - group name from feedbag",
  886. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  887. Items: []wire.FeedbagItem{
  888. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  889. },
  890. },
  891. wantCmd: []string{"INSERTED2:b::alice:Work"},
  892. mockParams: mockParams{
  893. feedBagParams: feedBagParams{
  894. feedbagParams: feedbagParams{
  895. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Work"}}, err: nil},
  896. },
  897. },
  898. },
  899. },
  900. {
  901. name: "one buddy with alias",
  902. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  903. Items: []wire.FeedbagItem{buddyWithAlias},
  904. },
  905. wantCmd: []string{"INSERTED2:b:Alice N.:alice:Work"},
  906. mockParams: mockParams{
  907. feedBagParams: feedBagParams{
  908. feedbagParams: feedbagParams{
  909. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Work"}}, err: nil},
  910. },
  911. },
  912. },
  913. },
  914. {
  915. name: "one buddy - unknown group ID uses Buddies",
  916. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  917. Items: []wire.FeedbagItem{
  918. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 999, Name: "bob"},
  919. },
  920. },
  921. wantCmd: []string{"INSERTED2:b::bob:Buddies"},
  922. mockParams: mockParams{
  923. feedBagParams: feedBagParams{
  924. feedbagParams: feedbagParams{
  925. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{}, err: nil},
  926. },
  927. },
  928. },
  929. },
  930. {
  931. name: "two buddies same group",
  932. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  933. Items: []wire.FeedbagItem{
  934. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  935. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "bob"},
  936. },
  937. },
  938. wantCmd: []string{"INSERTED2:b::alice:Friends", "INSERTED2:b::bob:Friends"},
  939. mockParams: mockParams{
  940. feedBagParams: feedBagParams{
  941. feedbagParams: feedbagParams{
  942. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Friends"}}, err: nil},
  943. },
  944. },
  945. },
  946. },
  947. {
  948. name: "group added",
  949. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  950. Items: []wire.FeedbagItem{
  951. {ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "NewGroup"},
  952. },
  953. },
  954. wantCmd: []string{"INSERTED2:g:NewGroup"},
  955. mockParams: mockParams{},
  956. },
  957. {
  958. name: "permit and deny added",
  959. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  960. Items: []wire.FeedbagItem{
  961. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice"},
  962. {ItemID: 2, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "bob"},
  963. },
  964. },
  965. wantCmd: []string{"INSERTED2:p:alice", "INSERTED2:d:bob"},
  966. mockParams: mockParams{},
  967. },
  968. {
  969. name: "all four types - group buddy deny permit",
  970. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  971. Items: []wire.FeedbagItem{
  972. {ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 200, Name: "NewGroup"},
  973. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  974. {ItemID: 3, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "blockedUser"},
  975. {ItemID: 4, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "allowedUser"},
  976. },
  977. },
  978. wantCmd: []string{"INSERTED2:g:NewGroup", "INSERTED2:b::alice:Buddies", "INSERTED2:d:blockedUser", "INSERTED2:p:allowedUser"},
  979. mockParams: mockParams{
  980. feedBagParams: feedBagParams{
  981. feedbagParams: feedbagParams{
  982. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Buddies"}}, err: nil},
  983. },
  984. },
  985. },
  986. },
  987. {
  988. name: "feedbag lookup fails",
  989. snac: wire.SNAC_0x13_0x09_FeedbagUpdateItem{
  990. Items: []wire.FeedbagItem{
  991. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  992. },
  993. },
  994. wantCmd: nil,
  995. mockParams: mockParams{
  996. feedBagParams: feedBagParams{
  997. feedbagParams: feedbagParams{
  998. {screenName: state.NewIdentScreenName("me"), results: nil, err: assert.AnError},
  999. },
  1000. },
  1001. },
  1002. },
  1003. }
  1004. for _, tc := range cases {
  1005. t.Run(tc.name, func(t *testing.T) {
  1006. ctx, cancel := context.WithCancel(context.Background())
  1007. fbMgr := newMockFeedbagManager(t)
  1008. for _, params := range tc.mockParams.feedbagParams {
  1009. fbMgr.EXPECT().
  1010. Feedbag(mock.Anything, params.screenName).
  1011. Return(params.results, params.err)
  1012. }
  1013. svc := testOSCARProxy(t)
  1014. svc.FeedbagManager = fbMgr
  1015. me := newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) })
  1016. ch := make(chan []string)
  1017. wg := &sync.WaitGroup{}
  1018. wg.Add(1)
  1019. go func() {
  1020. defer wg.Done()
  1021. err := svc.RecvBOS(ctx, me, NewChatRegistry(), ch)
  1022. assert.NoError(t, err)
  1023. }()
  1024. status := me.RelayMessageToInstance(wire.SNACMessage{Body: tc.snac})
  1025. assert.Equal(t, state.SessSendOK, status)
  1026. gotCmd := <-ch
  1027. assert.Equal(t, tc.wantCmd, gotCmd)
  1028. cancel()
  1029. wg.Wait()
  1030. })
  1031. }
  1032. }
  1033. func TestOSCARProxy_RecvBOS_Deleted2(t *testing.T) {
  1034. cases := []struct {
  1035. name string
  1036. snac wire.SNAC_0x13_0x0A_FeedbagDeleteItem
  1037. wantCmd []string
  1038. mockParams mockParams
  1039. }{
  1040. {
  1041. name: "one buddy removed",
  1042. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1043. Items: []wire.FeedbagItem{
  1044. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  1045. },
  1046. },
  1047. wantCmd: []string{"DELETED2:b:alice:Buddies"},
  1048. mockParams: mockParams{
  1049. feedBagParams: feedBagParams{
  1050. feedbagParams: feedbagParams{
  1051. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Buddies"}}, err: nil},
  1052. },
  1053. },
  1054. },
  1055. },
  1056. {
  1057. name: "two buddies removed",
  1058. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1059. Items: []wire.FeedbagItem{
  1060. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  1061. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "bob"},
  1062. },
  1063. },
  1064. wantCmd: []string{"DELETED2:b:alice:Friends", "DELETED2:b:bob:Friends"},
  1065. mockParams: mockParams{
  1066. feedBagParams: feedBagParams{
  1067. feedbagParams: feedbagParams{
  1068. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Friends"}}, err: nil},
  1069. },
  1070. },
  1071. },
  1072. },
  1073. {
  1074. name: "one buddy - unknown group ID uses Buddies",
  1075. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1076. Items: []wire.FeedbagItem{
  1077. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 999, Name: "alice"},
  1078. },
  1079. },
  1080. wantCmd: []string{"DELETED2:b:alice:Buddies"},
  1081. mockParams: mockParams{
  1082. feedBagParams: feedBagParams{
  1083. feedbagParams: feedbagParams{
  1084. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{}, err: nil},
  1085. },
  1086. },
  1087. },
  1088. },
  1089. {
  1090. name: "permit and deny removed",
  1091. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1092. Items: []wire.FeedbagItem{
  1093. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice"},
  1094. {ItemID: 2, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "bob"},
  1095. },
  1096. },
  1097. wantCmd: []string{"DELETED2:p:alice", "DELETED2:d:bob"},
  1098. mockParams: mockParams{},
  1099. },
  1100. {
  1101. name: "group deleted",
  1102. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1103. Items: []wire.FeedbagItem{
  1104. {ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Work"},
  1105. },
  1106. },
  1107. wantCmd: []string{"DELETED2:g:Work"},
  1108. mockParams: mockParams{},
  1109. },
  1110. {
  1111. name: "mix of buddy permit and deny emitted",
  1112. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1113. Items: []wire.FeedbagItem{
  1114. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "permitUser"},
  1115. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "buddyUser"},
  1116. },
  1117. },
  1118. wantCmd: []string{"DELETED2:p:permitUser", "DELETED2:b:buddyUser:Work"},
  1119. mockParams: mockParams{
  1120. feedBagParams: feedBagParams{
  1121. feedbagParams: feedbagParams{
  1122. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Work"}}, err: nil},
  1123. },
  1124. },
  1125. },
  1126. },
  1127. {
  1128. name: "all four types - group buddy deny permit",
  1129. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1130. Items: []wire.FeedbagItem{
  1131. {ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 200, Name: "OldGroup"},
  1132. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  1133. {ItemID: 3, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "blockedUser"},
  1134. {ItemID: 4, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "allowedUser"},
  1135. },
  1136. },
  1137. wantCmd: []string{"DELETED2:g:OldGroup", "DELETED2:b:alice:Buddies", "DELETED2:d:blockedUser", "DELETED2:p:allowedUser"},
  1138. mockParams: mockParams{
  1139. feedBagParams: feedBagParams{
  1140. feedbagParams: feedbagParams{
  1141. {screenName: state.NewIdentScreenName("me"), results: []wire.FeedbagItem{{ItemID: 1, ClassID: wire.FeedbagClassIdGroup, GroupID: 100, Name: "Buddies"}}, err: nil},
  1142. },
  1143. },
  1144. },
  1145. },
  1146. {
  1147. name: "empty items",
  1148. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{Items: nil},
  1149. wantCmd: nil,
  1150. mockParams: mockParams{},
  1151. },
  1152. {
  1153. name: "feedbag lookup fails",
  1154. snac: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  1155. Items: []wire.FeedbagItem{
  1156. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice"},
  1157. },
  1158. },
  1159. wantCmd: nil,
  1160. mockParams: mockParams{
  1161. feedBagParams: feedBagParams{
  1162. feedbagParams: feedbagParams{
  1163. {screenName: state.NewIdentScreenName("me"), results: nil, err: assert.AnError},
  1164. },
  1165. },
  1166. },
  1167. },
  1168. }
  1169. for _, tc := range cases {
  1170. t.Run(tc.name, func(t *testing.T) {
  1171. ctx, cancel := context.WithCancel(context.Background())
  1172. fbMgr := newMockFeedbagManager(t)
  1173. for _, params := range tc.mockParams.feedbagParams {
  1174. fbMgr.EXPECT().
  1175. Feedbag(mock.Anything, params.screenName).
  1176. Return(params.results, params.err)
  1177. }
  1178. svc := testOSCARProxy(t)
  1179. svc.FeedbagManager = fbMgr
  1180. me := newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) })
  1181. ch := make(chan []string)
  1182. wg := &sync.WaitGroup{}
  1183. wg.Add(1)
  1184. go func() {
  1185. defer wg.Done()
  1186. err := svc.RecvBOS(ctx, me, NewChatRegistry(), ch)
  1187. assert.NoError(t, err)
  1188. }()
  1189. status := me.RelayMessageToInstance(wire.SNACMessage{Body: tc.snac})
  1190. assert.Equal(t, state.SessSendOK, status)
  1191. gotCmd := <-ch
  1192. assert.Equal(t, tc.wantCmd, gotCmd)
  1193. cancel()
  1194. wg.Wait()
  1195. })
  1196. }
  1197. }
  1198. func testOSCARProxy(t *testing.T) OSCARProxy {
  1199. buddyService := newMockBuddyService(t)
  1200. buddyService.EXPECT().
  1201. BroadcastBuddyDeparted(mock.Anything, mock.Anything).
  1202. Maybe().
  1203. Return(nil)
  1204. buddyListRegistry := newMockBuddyListRegistry(t)
  1205. buddyListRegistry.EXPECT().
  1206. UnregisterBuddyList(mock.Anything, mock.Anything).
  1207. Maybe().
  1208. Return(nil)
  1209. authService := newMockAuthService(t)
  1210. authService.EXPECT().
  1211. Signout(mock.Anything, mock.Anything).
  1212. Maybe()
  1213. authService.EXPECT().
  1214. SignoutChat(mock.Anything, mock.Anything).
  1215. Maybe()
  1216. return OSCARProxy{
  1217. AuthService: authService,
  1218. BuddyListRegistry: buddyListRegistry,
  1219. BuddyService: buddyService,
  1220. Logger: slog.Default(),
  1221. }
  1222. }