snacs_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. package wire
  2. import (
  3. "bytes"
  4. "testing"
  5. "github.com/google/uuid"
  6. "github.com/stretchr/testify/assert"
  7. )
  8. func TestBARTInfo_HasClearIconHash(t *testing.T) {
  9. tests := []struct {
  10. name string
  11. bartInfo BARTInfo
  12. want bool
  13. }{
  14. {
  15. bartInfo: BARTInfo{
  16. Hash: GetClearIconHash(),
  17. },
  18. want: true,
  19. },
  20. {
  21. bartInfo: BARTInfo{
  22. Hash: []byte{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'},
  23. },
  24. want: false,
  25. },
  26. }
  27. for _, tt := range tests {
  28. t.Run(tt.name, func(t *testing.T) {
  29. assert.Equal(t, tt.want, tt.bartInfo.HasClearIconHash())
  30. })
  31. }
  32. }
  33. func TestBARTID_StatusText(t *testing.T) {
  34. marshaled := func(t *testing.T, status string) []byte {
  35. t.Helper()
  36. b := &bytes.Buffer{}
  37. assert.NoError(t, MarshalBE(bartStatus{Status: status}, b))
  38. return b.Bytes()
  39. }
  40. tests := []struct {
  41. name string
  42. id BARTID
  43. want string
  44. // wantErr reports whether the item's payload fails to decode.
  45. wantErr bool
  46. }{
  47. {
  48. name: "a data-bearing status item carries its text",
  49. id: BARTID{
  50. Type: BARTTypesStatusStr,
  51. BARTInfo: BARTInfo{Flags: BARTFlagsData, Hash: marshaled(t, "out to lunch")},
  52. },
  53. want: "out to lunch",
  54. },
  55. {
  56. name: "empty text reads as no status message",
  57. id: BARTID{
  58. Type: BARTTypesStatusStr,
  59. BARTInfo: BARTInfo{Flags: BARTFlagsData, Hash: marshaled(t, "")},
  60. },
  61. },
  62. {
  63. name: "a status item without the data flag reads as no status message",
  64. id: BARTID{
  65. Type: BARTTypesStatusStr,
  66. BARTInfo: BARTInfo{Flags: BARTFlagsKnown, Hash: marshaled(t, "out to lunch")},
  67. },
  68. },
  69. {
  70. name: "another BART type is not a status message",
  71. id: BARTID{
  72. Type: BARTTypesBuddyIcon,
  73. BARTInfo: BARTInfo{Flags: BARTFlagsData, Hash: marshaled(t, "out to lunch")},
  74. },
  75. },
  76. {
  77. name: "a malformed item is an error",
  78. id: BARTID{
  79. Type: BARTTypesStatusStr,
  80. BARTInfo: BARTInfo{Flags: BARTFlagsData, Hash: []byte{0x00}},
  81. },
  82. wantErr: true,
  83. },
  84. }
  85. for _, tt := range tests {
  86. t.Run(tt.name, func(t *testing.T) {
  87. got, err := tt.id.StatusText()
  88. if tt.wantErr {
  89. assert.Error(t, err)
  90. } else {
  91. assert.NoError(t, err)
  92. }
  93. assert.Equal(t, tt.want, got)
  94. })
  95. }
  96. }
  97. func TestSNAC_0x01_0x14_OServiceSetPrivacyFlags_IdleFlag(t *testing.T) {
  98. type fields struct {
  99. PrivacyFlags uint32
  100. }
  101. tests := []struct {
  102. name string
  103. fields fields
  104. want bool
  105. }{
  106. {
  107. name: "flag is set",
  108. fields: fields{
  109. PrivacyFlags: OServicePrivacyFlagIdle | OServicePrivacyFlagMember,
  110. },
  111. want: true,
  112. },
  113. {
  114. name: "flag is not set",
  115. fields: fields{
  116. PrivacyFlags: OServicePrivacyFlagMember,
  117. },
  118. want: false,
  119. },
  120. }
  121. for _, tt := range tests {
  122. t.Run(tt.name, func(t *testing.T) {
  123. s := SNAC_0x01_0x14_OServiceSetPrivacyFlags{
  124. PrivacyFlags: tt.fields.PrivacyFlags,
  125. }
  126. assert.Equal(t, tt.want, s.IdleFlag())
  127. })
  128. }
  129. }
  130. func TestSNAC_0x01_0x14_OServiceSetPrivacyFlags_MemberFlag(t *testing.T) {
  131. type fields struct {
  132. PrivacyFlags uint32
  133. }
  134. tests := []struct {
  135. name string
  136. fields fields
  137. want bool
  138. }{
  139. {
  140. name: "flag is set",
  141. fields: fields{
  142. PrivacyFlags: OServicePrivacyFlagIdle | OServicePrivacyFlagMember,
  143. },
  144. want: true,
  145. },
  146. {
  147. name: "flag is not set",
  148. fields: fields{
  149. PrivacyFlags: OServicePrivacyFlagIdle,
  150. },
  151. want: false,
  152. },
  153. }
  154. for _, tt := range tests {
  155. t.Run(tt.name, func(t *testing.T) {
  156. s := SNAC_0x01_0x14_OServiceSetPrivacyFlags{
  157. PrivacyFlags: tt.fields.PrivacyFlags,
  158. }
  159. assert.Equal(t, tt.want, s.MemberFlag())
  160. })
  161. }
  162. }
  163. func TestUnmarshalChatMessageText(t *testing.T) {
  164. tests := []struct {
  165. name string
  166. b []byte
  167. want string
  168. wantErr string
  169. }{
  170. {
  171. name: "happy path",
  172. b: func() []byte {
  173. tlv := TLVRestBlock{
  174. TLVList: TLVList{
  175. NewTLVBE(ChatTLVMessageInfoText, "<p>hello world!</p>"),
  176. },
  177. }
  178. b := &bytes.Buffer{}
  179. err := MarshalBE(tlv, b)
  180. assert.NoError(t, err)
  181. return b.Bytes()
  182. }(),
  183. want: "<p>hello world!</p>",
  184. },
  185. {
  186. name: "missing ChatTLVMessageInfoText",
  187. b: func() []byte {
  188. tlv := TLVRestBlock{TLVList: TLVList{}}
  189. b := &bytes.Buffer{}
  190. err := MarshalBE(tlv, b)
  191. assert.NoError(t, err)
  192. return b.Bytes()
  193. }(),
  194. wantErr: "has no chat msg text TLV",
  195. },
  196. }
  197. for _, tt := range tests {
  198. t.Run(tt.name, func(t *testing.T) {
  199. got, err := UnmarshalChatMessageText(tt.b)
  200. if tt.wantErr != "" {
  201. assert.ErrorContains(t, err, tt.wantErr)
  202. } else {
  203. assert.NoError(t, err)
  204. assert.Equal(t, tt.want, got)
  205. }
  206. })
  207. }
  208. }
  209. func TestTLVUserInfo_IsAway(t *testing.T) {
  210. tests := []struct {
  211. name string
  212. userInfo TLVUserInfo
  213. want bool
  214. }{
  215. {
  216. name: "flag is set",
  217. userInfo: TLVUserInfo{
  218. TLVBlock: TLVBlock{
  219. TLVList: TLVList{
  220. NewTLVBE(OServiceUserInfoUserFlags, OServiceUserFlagUnavailable),
  221. },
  222. },
  223. },
  224. want: true,
  225. },
  226. {
  227. name: "flag is not set",
  228. userInfo: TLVUserInfo{
  229. TLVBlock: TLVBlock{
  230. TLVList: TLVList{
  231. NewTLVBE(OServiceUserInfoUserFlags, OServiceUserFlagOSCARFree),
  232. },
  233. },
  234. },
  235. want: false,
  236. },
  237. {
  238. name: "TLV is missing",
  239. userInfo: TLVUserInfo{
  240. TLVBlock: TLVBlock{
  241. TLVList: TLVList{},
  242. },
  243. },
  244. want: false,
  245. },
  246. }
  247. for _, tt := range tests {
  248. t.Run(tt.name, func(t *testing.T) {
  249. assert.Equal(t, tt.want, tt.userInfo.IsAway())
  250. })
  251. }
  252. }
  253. func TestTLVUserInfo_IsInvisible(t *testing.T) {
  254. tests := []struct {
  255. name string
  256. userInfo TLVUserInfo
  257. want bool
  258. }{
  259. {
  260. name: "status mask has invisible bit set",
  261. userInfo: TLVUserInfo{
  262. TLVBlock: TLVBlock{
  263. TLVList: TLVList{
  264. NewTLVBE(OServiceUserInfoStatus, OServiceUserStatusInvisible),
  265. },
  266. },
  267. },
  268. want: true,
  269. },
  270. {
  271. name: "status mask does not have invisible bit set",
  272. userInfo: TLVUserInfo{
  273. TLVBlock: TLVBlock{
  274. TLVList: TLVList{
  275. NewTLVBE(OServiceUserInfoStatus, OServiceUserStatusAvailable),
  276. },
  277. },
  278. },
  279. want: false,
  280. },
  281. {
  282. name: "TLV is missing",
  283. userInfo: TLVUserInfo{
  284. TLVBlock: TLVBlock{
  285. TLVList: TLVList{},
  286. },
  287. },
  288. want: false,
  289. },
  290. }
  291. for _, tt := range tests {
  292. t.Run(tt.name, func(t *testing.T) {
  293. assert.Equal(t, tt.want, tt.userInfo.IsInvisible())
  294. })
  295. }
  296. }
  297. func TestCapabilityUUIDs(t *testing.T) {
  298. tests := []struct {
  299. name string
  300. cap uuid.UUID
  301. expected string
  302. }{
  303. // Original capabilities
  304. {"CapChat", CapChat, "748f2420-6287-11d1-8222-444553540000"},
  305. {"CapFileTransfer", CapFileTransfer, "09461343-4c7f-11d1-8222-444553540000"},
  306. // Short caps
  307. {"CapShortCaps", CapShortCaps, "09460000-4c7f-11d1-8222-444553540000"},
  308. {"CapSecureIM", CapSecureIM, "09460001-4c7f-11d1-8222-444553540000"},
  309. {"CapXHTMLIM", CapXHTMLIM, "09460002-4c7f-11d1-8222-444553540000"},
  310. {"CapRTCVideo", CapRTCVideo, "09460101-4c7f-11d1-8222-444553540000"},
  311. {"CapHasCamera", CapHasCamera, "09460102-4c7f-11d1-8222-444553540000"},
  312. {"CapHasMicrophone", CapHasMicrophone, "09460103-4c7f-11d1-8222-444553540000"},
  313. {"CapRTCAudio", CapRTCAudio, "09460104-4c7f-11d1-8222-444553540000"},
  314. {"CapHostStatusTextAware", CapHostStatusTextAware, "0946010a-4c7f-11d1-8222-444553540000"},
  315. {"CapRTIM", CapRTIM, "0946010b-4c7f-11d1-8222-444553540000"},
  316. {"CapSmartCaps", CapSmartCaps, "094601ff-4c7f-11d1-8222-444553540000"},
  317. {"CapVoiceChat", CapVoiceChat, "09461341-4c7f-11d1-8222-444553540000"},
  318. {"CapDirectPlay", CapDirectPlay, "09461342-4c7f-11d1-8222-444553540000"},
  319. {"CapRouteFinder", CapRouteFinder, "09461344-4c7f-11d1-8222-444553540000"},
  320. {"CapDirectICBM", CapDirectICBM, "09461345-4c7f-11d1-8222-444553540000"},
  321. {"CapAvatarService", CapAvatarService, "09461346-4c7f-11d1-8222-444553540000"},
  322. {"CapStocksAddins", CapStocksAddins, "09461347-4c7f-11d1-8222-444553540000"},
  323. {"CapFileSharing", CapFileSharing, "09461348-4c7f-11d1-8222-444553540000"},
  324. {"CapICQCh2Extended", CapICQCh2Extended, "09461349-4c7f-11d1-8222-444553540000"},
  325. {"CapGames", CapGames, "0946134a-4c7f-11d1-8222-444553540000"},
  326. {"CapBuddyListTransfer", CapBuddyListTransfer, "0946134b-4c7f-11d1-8222-444553540000"},
  327. {"CapSupportICQ", CapSupportICQ, "0946134d-4c7f-11d1-8222-444553540000"},
  328. {"CapUTF8Messages", CapUTF8Messages, "0946134e-4c7f-11d1-8222-444553540000"},
  329. // Full UUIDs
  330. {"CapRTFMessages", CapRTFMessages, "97b12751-243c-4334-ad22-d6abf73f1492"},
  331. {"CapTrillianSecureIM", CapTrillianSecureIM, "f2e7c7f4-fead-4dfb-b235-36798bdf0000"},
  332. {"CapXtrazScript", CapXtrazScript, "3b60b3ef-d82a-6c45-a4e0-9c5a5e67e865"},
  333. // Unknown/Legacy
  334. {"CapUnknownICQ2001_2002", CapUnknownICQ2001_2002, "a0e93f37-4c7f-11d1-8222-444553540000"},
  335. {"CapUnknownICQ2002", CapUnknownICQ2002, "10cf40d1-4c7f-11d1-8222-444553540000"},
  336. {"CapUnknownICQ2001", CapUnknownICQ2001, "2e7a6475-fadf-4dc8-886f-ea3595fdb6df"},
  337. {"CapUnknownICQLite", CapUnknownICQLite, "563fc809-0b6f-41bd-9f79-422609dfa2f3"},
  338. {"CapGamesAlt", CapGamesAlt, "0946134a-4c7f-11d1-2282-444553540000"},
  339. }
  340. for _, tt := range tests {
  341. t.Run(tt.name, func(t *testing.T) {
  342. assert.Equal(t, tt.expected, tt.cap.String())
  343. })
  344. }
  345. }
  346. func TestShortCapHexToUUID(t *testing.T) {
  347. tests := []struct {
  348. name string
  349. hexStr string
  350. wantUUID uuid.UUID
  351. wantOK bool
  352. }{
  353. {"1348 -> CapFileSharing", "1348", CapFileSharing, true},
  354. {"134B -> CapBuddyListTransfer", "134B", CapBuddyListTransfer, true},
  355. {"1FF -> CapSmartCaps", "1FF", CapSmartCaps, true},
  356. {"1341 -> CapVoiceChat", "1341", CapVoiceChat, true},
  357. {"0000 -> CapShortCaps", "0000", CapShortCaps, true},
  358. {"empty string", "", uuid.Nil, false},
  359. {"invalid hex", "GGGG", uuid.Nil, false},
  360. {"too large for uint16", "10000", uuid.Nil, false},
  361. {"whitespace trimmed", " 1348 ", CapFileSharing, true},
  362. }
  363. for _, tt := range tests {
  364. t.Run(tt.name, func(t *testing.T) {
  365. got, ok := ShortCapHexToUUID(tt.hexStr)
  366. assert.Equal(t, tt.wantOK, ok)
  367. if tt.wantOK {
  368. assert.Equal(t, tt.wantUUID, got)
  369. } else {
  370. assert.Equal(t, uuid.Nil, got)
  371. }
  372. })
  373. }
  374. }
  375. func TestFeedbagItem_AppendOrderMembers(t *testing.T) {
  376. t.Run("creates order TLV when none exists", func(t *testing.T) {
  377. item := FeedbagItem{ClassID: FeedbagClassIdGroup, GroupID: 1}
  378. item.AppendOrderMembers(10, 20)
  379. order, ok := item.Uint16SliceBE(FeedbagAttributesOrder)
  380. assert.True(t, ok)
  381. assert.Equal(t, []uint16{10, 20}, order)
  382. })
  383. t.Run("appends to existing order TLV", func(t *testing.T) {
  384. item := FeedbagItem{
  385. ClassID: FeedbagClassIdGroup,
  386. GroupID: 1,
  387. TLVLBlock: TLVLBlock{
  388. TLVList: TLVList{
  389. NewTLVBE(FeedbagAttributesOrder, []uint16{5, 10}),
  390. },
  391. },
  392. }
  393. item.AppendOrderMembers(15, 20)
  394. order, ok := item.Uint16SliceBE(FeedbagAttributesOrder)
  395. assert.True(t, ok)
  396. assert.Equal(t, []uint16{5, 10, 15, 20}, order)
  397. })
  398. t.Run("single member", func(t *testing.T) {
  399. item := FeedbagItem{ClassID: FeedbagClassIdGroup}
  400. item.AppendOrderMembers(42)
  401. order, ok := item.Uint16SliceBE(FeedbagAttributesOrder)
  402. assert.True(t, ok)
  403. assert.Equal(t, []uint16{42}, order)
  404. })
  405. }
  406. func TestFeedbagItem_RemoveOrderMembers(t *testing.T) {
  407. t.Run("removes specified members", func(t *testing.T) {
  408. item := FeedbagItem{
  409. ClassID: FeedbagClassIdGroup,
  410. TLVLBlock: TLVLBlock{
  411. TLVList: TLVList{
  412. NewTLVBE(FeedbagAttributesOrder, []uint16{5, 10, 15, 20}),
  413. },
  414. },
  415. }
  416. item.RemoveOrderMembers(10, 20)
  417. order, ok := item.Uint16SliceBE(FeedbagAttributesOrder)
  418. assert.True(t, ok)
  419. assert.Equal(t, []uint16{5, 15}, order)
  420. })
  421. t.Run("no-op when order TLV does not exist", func(t *testing.T) {
  422. item := FeedbagItem{ClassID: FeedbagClassIdGroup}
  423. item.RemoveOrderMembers(10)
  424. _, ok := item.Uint16SliceBE(FeedbagAttributesOrder)
  425. assert.False(t, ok)
  426. })
  427. t.Run("removes all members", func(t *testing.T) {
  428. item := FeedbagItem{
  429. ClassID: FeedbagClassIdGroup,
  430. TLVLBlock: TLVLBlock{
  431. TLVList: TLVList{
  432. NewTLVBE(FeedbagAttributesOrder, []uint16{5, 10}),
  433. },
  434. },
  435. }
  436. item.RemoveOrderMembers(5, 10)
  437. order, ok := item.Uint16SliceBE(FeedbagAttributesOrder)
  438. assert.True(t, ok)
  439. assert.Empty(t, order)
  440. })
  441. t.Run("ignores IDs not in order", func(t *testing.T) {
  442. item := FeedbagItem{
  443. ClassID: FeedbagClassIdGroup,
  444. TLVLBlock: TLVLBlock{
  445. TLVList: TLVList{
  446. NewTLVBE(FeedbagAttributesOrder, []uint16{5, 10}),
  447. },
  448. },
  449. }
  450. item.RemoveOrderMembers(99)
  451. order, ok := item.Uint16SliceBE(FeedbagAttributesOrder)
  452. assert.True(t, ok)
  453. assert.Equal(t, []uint16{5, 10}, order)
  454. })
  455. }
  456. func TestAllASCII(t *testing.T) {
  457. tests := []struct {
  458. name string
  459. msg string
  460. want bool
  461. }{
  462. {name: "empty", msg: "", want: true},
  463. {name: "ASCII", msg: "hello world!", want: true},
  464. {name: "DEL is ASCII", msg: "\x7f", want: true},
  465. {name: "Latin-1 byte", msg: "\xe9", want: false},
  466. {name: "multibyte UTF-8", msg: "héllo", want: false},
  467. }
  468. for _, tt := range tests {
  469. t.Run(tt.name, func(t *testing.T) {
  470. assert.Equal(t, tt.want, AllASCII(tt.msg))
  471. assert.Equal(t, tt.want, AllASCII([]byte(tt.msg)))
  472. })
  473. }
  474. }
  475. func TestICBMFragmentList(t *testing.T) {
  476. tests := []struct {
  477. name string
  478. text string
  479. want func(string) ([]ICBMCh1Fragment, error)
  480. }{
  481. {name: "ASCII text is encoded as ASCII", text: "hello world!", want: ICBMFragmentListASCII},
  482. {name: "empty text is encoded as ASCII", text: "", want: ICBMFragmentListASCII},
  483. {name: "non-ASCII text is encoded as UCS-2", text: "héllo 日本", want: ICBMFragmentListUnicode},
  484. }
  485. for _, tt := range tests {
  486. t.Run(tt.name, func(t *testing.T) {
  487. want, err := tt.want(tt.text)
  488. assert.NoError(t, err)
  489. got, err := ICBMFragmentList(tt.text)
  490. assert.NoError(t, err)
  491. assert.Equal(t, want, got)
  492. })
  493. }
  494. }