buddy_prefs.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. package wire
  2. import "slices"
  3. // Buddy preferences are boolean values stored in a single feedbag item of class
  4. // FeedbagClassIdBuddyPrefs. Each preference is a bit position in a logical
  5. // bitmask spanning two physical bitmasks, each paired with a "valid" bitmask
  6. // that records which positions carry a meaningful value.
  7. //
  8. // The first bitmask (BuddyPrefs, 0x00C9) is fixed at 32 bits (4 bytes) and is
  9. // 0-offset with the most significant bit on the right side. The second bitmask
  10. // (BuddyPrefs2, 0x00D7) is unbounded, positional, with the most significant bit
  11. // on the left side.
  12. //
  13. // Preferences 0-31 live in BuddyPrefs:
  14. //
  15. // Pref #1:
  16. // 00000000 00000000 00000000 00000010 (BuddyPrefs)
  17. // ^ offset 1, bit 2
  18. //
  19. // Pref #31:
  20. // 10000000 00000000 00000000 00000000 (BuddyPrefs)
  21. // ^ offset 31, bit 32
  22. //
  23. // Preferences 32+ live in BuddyPrefs2. The bit offset is (prefNum-33), except
  24. // for the edge case of prefs 32 and 33, which both fall at offset 0 (an
  25. // artifact of the transition from offset- to position-based indexing):
  26. //
  27. // Pref #52:
  28. // 00000000 00000000 00010000 00000000 (BuddyPrefs2)
  29. // ^ offset 19, bit 52
  30. //
  31. // For each logical bitmask there are two physical bitmasks: the value bitmask
  32. // and the valid bitmask. The valid bitmask disambiguates an unset position:
  33. // i.e. whether an unset value means false or "not present".
  34. //
  35. // The bitmasks are carried in four TLVs:
  36. //
  37. // - 0x00C9: FeedbagAttributesBuddyPrefs (value, prefs 0-31)
  38. // - 0x00D6: FeedbagAttributesBuddyPrefsValid (valid, prefs 0-31)
  39. // - 0x00D7: FeedbagAttributesBuddyPrefs2 (value, prefs 32+)
  40. // - 0x00D8: FeedbagAttributesBuddyPrefs2Valid (valid, prefs 32+)
  41. // Buddy preference bit numbers. OSCAR defines preferences through
  42. // FeedbagBuddyPrefsImblastInviteNotify (0x4B).
  43. const (
  44. FeedbagBuddyPrefsDisplayLogin uint16 = 0x00
  45. FeedbagBuddyPrefsDisplayEBuddy uint16 = 0x01
  46. FeedbagBuddyPrefsPlayEnter uint16 = 0x02
  47. FeedbagBuddyPrefsPlayExit uint16 = 0x03
  48. FeedbagBuddyPrefsViewIMStamp uint16 = 0x04
  49. FeedbagBuddyPrefsViewSmileys uint16 = 0x05
  50. FeedbagBuddyPrefsAcceptIcons uint16 = 0x06
  51. FeedbagBuddyPrefsKnockNonAOLIMs uint16 = 0x08
  52. FeedbagBuddyPrefsKnockNonListIMs uint16 = 0x09
  53. FeedbagBuddyPrefsDiscloseIdle uint16 = 0x0A
  54. FeedbagBuddyPrefsAcceptCustomBart uint16 = 0x0B
  55. FeedbagBuddyPrefsAcceptNonListBart uint16 = 0x0C
  56. FeedbagBuddyPrefsAcceptBgs uint16 = 0x0D
  57. FeedbagBuddyPrefsAcceptChromes uint16 = 0x0E
  58. FeedbagBuddyPrefsAcceptBLSounds uint16 = 0x0F
  59. FeedbagBuddyPrefsAcceptIMSounds uint16 = 0x10
  60. FeedbagBuddyPrefsNoSeeRecentBuddies uint16 = 0x11
  61. FeedbagBuddyPrefsAcceptSMSLegal uint16 = 0x12
  62. FeedbagBuddyPrefsEnterDoesCRLF uint16 = 0x14
  63. FeedbagBuddyPrefsPlayIMSound uint16 = 0x15
  64. FeedbagBuddyPrefsDiscloseTyping uint16 = 0x16
  65. FeedbagBuddyPrefsAcceptSuperIcons uint16 = 0x18
  66. FeedbagBuddyPrefsAcceptBLRichText uint16 = 0x19
  67. FeedbagBuddyPrefsReduceIMSound uint16 = 0x1A
  68. FeedbagBuddyPrefsConfirmDirectIM uint16 = 0x1B
  69. FeedbagBuddyPrefsOneTabbedIMWindow uint16 = 0x1C
  70. FeedbagBuddyPrefsBuddyInfoOnMouseover uint16 = 0x1D
  71. FeedbagBuddyPrefsDiscloseBuddyMatches uint16 = 0x1E
  72. FeedbagBuddyPrefsCatchIMs uint16 = 0x1F
  73. FeedbagBuddyPrefsShowFriendlyName uint16 = 0x20
  74. FeedbagBuddyPrefsDiscloseRadio uint16 = 0x21
  75. FeedbagBuddyPrefsShowCapabilities uint16 = 0x22
  76. FeedbagBuddyPrefsShowBuddyListFilter uint16 = 0x23
  77. FeedbagBuddyPrefsShowAwayIdle uint16 = 0x24
  78. FeedbagBuddyPrefsShowMobile uint16 = 0x25
  79. FeedbagBuddyPrefsSortBuddyList uint16 = 0x26
  80. FeedbagBuddyPrefsCatchIMsForClient uint16 = 0x27
  81. FeedbagBuddyPrefsNewMessageSmallNotify uint16 = 0x28
  82. FeedbagBuddyPrefsNoFrequentBuddies uint16 = 0x29
  83. FeedbagBuddyPrefsBlogAwayMessages uint16 = 0x2A
  84. FeedbagBuddyPrefsBlogAIMSigMessages uint16 = 0x2B
  85. FeedbagBuddyPrefsBlogNoComments uint16 = 0x2C
  86. FeedbagBuddyPrefsFriendOfFriend uint16 = 0x2D
  87. FeedbagBuddyPrefsFriendGetContactList uint16 = 0x2E
  88. FeedbagBuddyPrefsCompadInit uint16 = 0x2F
  89. FeedbagBuddyPrefsSendBuddyFeed uint16 = 0x30
  90. FeedbagBuddyPrefsBlkSendIMWhileAway uint16 = 0x31
  91. FeedbagBuddyPrefsShowBuddyFeed uint16 = 0x32
  92. FeedbagBuddyPrefsNoSaveVanityInfo uint16 = 0x33
  93. FeedbagBuddyPrefsAcceptOfflineIM uint16 = 0x34
  94. FeedbagBuddyPrefsShowGroups uint16 = 0x35
  95. FeedbagBuddyPrefsSortGroup uint16 = 0x36
  96. FeedbagBuddyPrefsShowOfflineBuddies uint16 = 0x37
  97. FeedbagBuddyPrefsExpandBuddies uint16 = 0x38
  98. FeedbagBuddyPrefsThirdPartyFeeds uint16 = 0x39
  99. FeedbagBuddyPrefsNotifyReceivedInvite uint16 = 0x3A
  100. FeedbagBuddyPrefsApfAutoAccept uint16 = 0x3B
  101. FeedbagBuddyPrefsApfAutoAcceptBuddy uint16 = 0x3C
  102. FeedbagBuddyPrefsBlockAwayMsgFeed uint16 = 0x3D
  103. FeedbagBuddyPrefsBlockAIMProfileFeed uint16 = 0x3E
  104. FeedbagBuddyPrefsBlockAIMPagesFeed uint16 = 0x3F
  105. FeedbagBuddyPrefsBlockJournalsFeed uint16 = 0x40
  106. FeedbagBuddyPrefsBlockLocationFeed uint16 = 0x41
  107. FeedbagBuddyPrefsBlockStickiesFeed uint16 = 0x42
  108. FeedbagBuddyPrefsBlockUncutFeed uint16 = 0x43
  109. FeedbagBuddyPrefsBlockLinksFeed uint16 = 0x44
  110. FeedbagBuddyPrefsBlockAIMBulletinFeed uint16 = 0x45
  111. FeedbagBuddyPrefsSaveStatusMsg uint16 = 0x46
  112. FeedbagBuddyPrefsApfNotifyReceivedByEmail uint16 = 0x47
  113. FeedbagBuddyPrefsShowOfflineGrp uint16 = 0x48
  114. FeedbagBuddyPrefsOfflineGrpCollapsed uint16 = 0x49
  115. FeedbagBuddyPrefsFirstIMSoundOnly uint16 = 0x4A
  116. FeedbagBuddyPrefsImblastInviteNotify uint16 = 0x4B
  117. )
  118. // Web-client-only preferences with no OSCAR equivalent. They are persisted in
  119. // the buddy-prefs bitmask at positions above OSCAR's range (0x4B); no real
  120. // OSCAR client reads or writes these bits.
  121. const (
  122. FeedbagBuddyPrefsViewIMsInBubbles uint16 = 0x4C
  123. FeedbagBuddyPrefsViewIMTimestampsRelative uint16 = 0x4D
  124. FeedbagBuddyPrefsGlobalOTR uint16 = 0x4E
  125. FeedbagBuddyPrefsImblastInviteFromBuddyOnly uint16 = 0x4F
  126. )
  127. // buddyPrefTags returns the (valid, value) TLV tags that hold prefNum.
  128. func buddyPrefTags(prefNum uint16) (validTag, valueTag uint16) {
  129. if prefNum < 32 {
  130. return FeedbagAttributesBuddyPrefsValid, FeedbagAttributesBuddyPrefs
  131. }
  132. return FeedbagAttributesBuddyPrefs2Valid, FeedbagAttributesBuddyPrefs2
  133. }
  134. // buddyPrefBit returns the byte index and bit mask for prefNum within a bitmask
  135. // of the given byte length.
  136. func buddyPrefBit(prefNum uint16, length int) (index int, mask byte) {
  137. if prefNum < 32 {
  138. // most significant bit on the right side
  139. index = (length - 1) - int(prefNum)/8
  140. return index, byte(1) << (prefNum % 8)
  141. }
  142. // most significant bit on the left side
  143. offset := 0
  144. if prefNum != 32 {
  145. offset = int(prefNum) - 33
  146. }
  147. index = offset / 8
  148. return index, byte(0x80) >> (offset % 8)
  149. }
  150. // buddyPrefDefaults is the source of truth for the default value of each buddy
  151. // preference: the value BuddyPref returns when the preference bit is absent from
  152. // the feedbag bitmask.
  153. var buddyPrefDefaults = map[uint16]bool{
  154. FeedbagBuddyPrefsDisplayLogin: true,
  155. FeedbagBuddyPrefsDisplayEBuddy: true,
  156. FeedbagBuddyPrefsPlayEnter: true,
  157. FeedbagBuddyPrefsPlayExit: true,
  158. FeedbagBuddyPrefsViewIMStamp: true,
  159. FeedbagBuddyPrefsViewSmileys: true,
  160. FeedbagBuddyPrefsAcceptIcons: true,
  161. FeedbagBuddyPrefsKnockNonAOLIMs: true,
  162. FeedbagBuddyPrefsKnockNonListIMs: true,
  163. FeedbagBuddyPrefsDiscloseIdle: true,
  164. FeedbagBuddyPrefsAcceptCustomBart: false,
  165. FeedbagBuddyPrefsAcceptNonListBart: false,
  166. FeedbagBuddyPrefsAcceptBgs: true,
  167. FeedbagBuddyPrefsAcceptChromes: true,
  168. FeedbagBuddyPrefsAcceptBLSounds: true,
  169. FeedbagBuddyPrefsAcceptIMSounds: true,
  170. FeedbagBuddyPrefsNoSeeRecentBuddies: false,
  171. FeedbagBuddyPrefsAcceptSMSLegal: false,
  172. FeedbagBuddyPrefsEnterDoesCRLF: false,
  173. FeedbagBuddyPrefsPlayIMSound: true,
  174. FeedbagBuddyPrefsDiscloseTyping: true,
  175. FeedbagBuddyPrefsAcceptSuperIcons: true,
  176. FeedbagBuddyPrefsAcceptBLRichText: true,
  177. FeedbagBuddyPrefsReduceIMSound: true,
  178. FeedbagBuddyPrefsConfirmDirectIM: true,
  179. FeedbagBuddyPrefsOneTabbedIMWindow: true,
  180. FeedbagBuddyPrefsBuddyInfoOnMouseover: true,
  181. FeedbagBuddyPrefsDiscloseBuddyMatches: true,
  182. FeedbagBuddyPrefsCatchIMs: false,
  183. FeedbagBuddyPrefsShowFriendlyName: true,
  184. FeedbagBuddyPrefsDiscloseRadio: true,
  185. FeedbagBuddyPrefsShowCapabilities: true,
  186. FeedbagBuddyPrefsShowBuddyListFilter: true,
  187. FeedbagBuddyPrefsShowAwayIdle: true,
  188. FeedbagBuddyPrefsShowMobile: true,
  189. FeedbagBuddyPrefsSortBuddyList: false,
  190. FeedbagBuddyPrefsCatchIMsForClient: false,
  191. FeedbagBuddyPrefsNewMessageSmallNotify: true,
  192. FeedbagBuddyPrefsNoFrequentBuddies: false,
  193. FeedbagBuddyPrefsBlogAwayMessages: false,
  194. FeedbagBuddyPrefsBlogAIMSigMessages: false,
  195. FeedbagBuddyPrefsBlogNoComments: false,
  196. FeedbagBuddyPrefsFriendOfFriend: false,
  197. FeedbagBuddyPrefsFriendGetContactList: false,
  198. FeedbagBuddyPrefsCompadInit: false,
  199. FeedbagBuddyPrefsSendBuddyFeed: true,
  200. FeedbagBuddyPrefsBlkSendIMWhileAway: false,
  201. FeedbagBuddyPrefsShowBuddyFeed: true,
  202. FeedbagBuddyPrefsNoSaveVanityInfo: false,
  203. FeedbagBuddyPrefsAcceptOfflineIM: true,
  204. // ShowGroups deliberately departs from the OSCAR spec default (0). The web
  205. // client keys group-header visibility off this pref and has no default of
  206. // its own, so we default it on to keep headers visible.
  207. FeedbagBuddyPrefsShowGroups: true,
  208. FeedbagBuddyPrefsSortGroup: true,
  209. FeedbagBuddyPrefsShowOfflineBuddies: true,
  210. FeedbagBuddyPrefsExpandBuddies: false,
  211. FeedbagBuddyPrefsThirdPartyFeeds: false,
  212. FeedbagBuddyPrefsNotifyReceivedInvite: true,
  213. FeedbagBuddyPrefsApfAutoAccept: false,
  214. FeedbagBuddyPrefsApfAutoAcceptBuddy: false,
  215. FeedbagBuddyPrefsBlockAwayMsgFeed: false,
  216. FeedbagBuddyPrefsBlockAIMProfileFeed: false,
  217. FeedbagBuddyPrefsBlockAIMPagesFeed: false,
  218. FeedbagBuddyPrefsBlockJournalsFeed: false,
  219. FeedbagBuddyPrefsBlockLocationFeed: false,
  220. FeedbagBuddyPrefsBlockStickiesFeed: false,
  221. FeedbagBuddyPrefsBlockUncutFeed: false,
  222. FeedbagBuddyPrefsBlockLinksFeed: false,
  223. FeedbagBuddyPrefsBlockAIMBulletinFeed: false,
  224. FeedbagBuddyPrefsSaveStatusMsg: true,
  225. FeedbagBuddyPrefsApfNotifyReceivedByEmail: false,
  226. FeedbagBuddyPrefsShowOfflineGrp: true,
  227. FeedbagBuddyPrefsOfflineGrpCollapsed: false,
  228. FeedbagBuddyPrefsFirstIMSoundOnly: false,
  229. FeedbagBuddyPrefsImblastInviteNotify: true,
  230. // Web-client-only preferences with no OSCAR equivalent.
  231. FeedbagBuddyPrefsViewIMsInBubbles: true,
  232. FeedbagBuddyPrefsViewIMTimestampsRelative: false,
  233. FeedbagBuddyPrefsGlobalOTR: false,
  234. FeedbagBuddyPrefsImblastInviteFromBuddyOnly: false,
  235. }
  236. // BuddyPref returns the effective boolean value of preference prefNum in a
  237. // feedbag buddy-prefs TLV list: the stored value when the preference is present
  238. // in the bitmask, otherwise the preference's default (see buddyPrefDefaults).
  239. // Whether the preference is actually present ("valid") is an internal detail;
  240. // BuddyPref is the source of truth for default values.
  241. func BuddyPref(list TLVList, prefNum uint16) bool {
  242. if valid, value := readBuddyPref(list, prefNum); valid {
  243. return value
  244. }
  245. return buddyPrefDefaults[prefNum]
  246. }
  247. // readBuddyPref reads preference prefNum from a feedbag buddy-prefs TLV list.
  248. // valid reports whether the preference is present in the bitmask; value is its
  249. // boolean value, meaningful only when valid is true.
  250. func readBuddyPref(list TLVList, prefNum uint16) (valid, value bool) {
  251. validTag, valueTag := buddyPrefTags(prefNum)
  252. validBytes, ok := list.Bytes(validTag)
  253. if !ok {
  254. return false, false
  255. }
  256. valueBytes, ok := list.Bytes(valueTag)
  257. if !ok {
  258. return false, false
  259. }
  260. index, mask := buddyPrefBit(prefNum, len(validBytes))
  261. if index < 0 || index >= len(validBytes) || index >= len(valueBytes) {
  262. return false, false
  263. }
  264. valid = validBytes[index]&mask != 0
  265. value = valueBytes[index]&mask != 0
  266. return valid, value
  267. }
  268. // SetBuddyPref returns list with preference prefNum set to value and marked
  269. // valid, growing the underlying bitmask byte slices as needed. The value and
  270. // valid TLVs are created if absent and other preference bits are preserved.
  271. func SetBuddyPref(list TLVList, prefNum uint16, value bool) TLVList {
  272. validTag, valueTag := buddyPrefTags(prefNum)
  273. // Clone so we never mutate a caller's backing array in place.
  274. rawValid, _ := list.Bytes(validTag)
  275. validBytes := slices.Clone(rawValid)
  276. rawValue, _ := list.Bytes(valueTag)
  277. valueBytes := slices.Clone(rawValue)
  278. // Normalize both bitmasks to a common length large enough to hold prefNum.
  279. var length int
  280. if prefNum < 32 {
  281. length = max(4, len(validBytes), len(valueBytes))
  282. validBytes = leftPad(validBytes, length)
  283. valueBytes = leftPad(valueBytes, length)
  284. } else {
  285. offset := 0
  286. if prefNum != 32 {
  287. offset = int(prefNum) - 33
  288. }
  289. length = max(offset/8+1, len(validBytes), len(valueBytes))
  290. validBytes = rightPad(validBytes, length)
  291. valueBytes = rightPad(valueBytes, length)
  292. }
  293. index, mask := buddyPrefBit(prefNum, length)
  294. validBytes[index] |= mask // a preference we set is always valid
  295. if value {
  296. valueBytes[index] |= mask
  297. } else {
  298. valueBytes[index] &^= mask
  299. }
  300. list.Set(NewTLVBE(validTag, validBytes))
  301. list.Set(NewTLVBE(valueTag, valueBytes))
  302. return list
  303. }
  304. // leftPad grows b to length n by prepending zero bytes (bit indexing is
  305. // right-aligned for the fixed BuddyPrefs bitmask).
  306. func leftPad(b []byte, n int) []byte {
  307. if len(b) >= n {
  308. return b
  309. }
  310. return append(make([]byte, n-len(b)), b...)
  311. }
  312. // rightPad grows b to length n by appending zero bytes (bit indexing is
  313. // left-aligned for the positional BuddyPrefs2 bitmask).
  314. func rightPad(b []byte, n int) []byte {
  315. if len(b) >= n {
  316. return b
  317. }
  318. return append(b, make([]byte, n-len(b))...)
  319. }