| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- package wire
- import "slices"
- // Buddy preferences are boolean values stored in a single feedbag item of class
- // FeedbagClassIdBuddyPrefs. Each preference is a bit position in a logical
- // bitmask spanning two physical bitmasks, each paired with a "valid" bitmask
- // that records which positions carry a meaningful value.
- //
- // The first bitmask (BuddyPrefs, 0x00C9) is fixed at 32 bits (4 bytes) and is
- // 0-offset with the most significant bit on the right side. The second bitmask
- // (BuddyPrefs2, 0x00D7) is unbounded, positional, with the most significant bit
- // on the left side.
- //
- // Preferences 0-31 live in BuddyPrefs:
- //
- // Pref #1:
- // 00000000 00000000 00000000 00000010 (BuddyPrefs)
- // ^ offset 1, bit 2
- //
- // Pref #31:
- // 10000000 00000000 00000000 00000000 (BuddyPrefs)
- // ^ offset 31, bit 32
- //
- // Preferences 32+ live in BuddyPrefs2. The bit offset is (prefNum-33), except
- // for the edge case of prefs 32 and 33, which both fall at offset 0 (an
- // artifact of the transition from offset- to position-based indexing):
- //
- // Pref #52:
- // 00000000 00000000 00010000 00000000 (BuddyPrefs2)
- // ^ offset 19, bit 52
- //
- // For each logical bitmask there are two physical bitmasks: the value bitmask
- // and the valid bitmask. The valid bitmask disambiguates an unset position:
- // i.e. whether an unset value means false or "not present".
- //
- // The bitmasks are carried in four TLVs:
- //
- // - 0x00C9: FeedbagAttributesBuddyPrefs (value, prefs 0-31)
- // - 0x00D6: FeedbagAttributesBuddyPrefsValid (valid, prefs 0-31)
- // - 0x00D7: FeedbagAttributesBuddyPrefs2 (value, prefs 32+)
- // - 0x00D8: FeedbagAttributesBuddyPrefs2Valid (valid, prefs 32+)
- // Buddy preference bit numbers. OSCAR defines preferences through
- // FeedbagBuddyPrefsImblastInviteNotify (0x4B).
- const (
- FeedbagBuddyPrefsDisplayLogin uint16 = 0x00
- FeedbagBuddyPrefsDisplayEBuddy uint16 = 0x01
- FeedbagBuddyPrefsPlayEnter uint16 = 0x02
- FeedbagBuddyPrefsPlayExit uint16 = 0x03
- FeedbagBuddyPrefsViewIMStamp uint16 = 0x04
- FeedbagBuddyPrefsViewSmileys uint16 = 0x05
- FeedbagBuddyPrefsAcceptIcons uint16 = 0x06
- FeedbagBuddyPrefsKnockNonAOLIMs uint16 = 0x08
- FeedbagBuddyPrefsKnockNonListIMs uint16 = 0x09
- FeedbagBuddyPrefsDiscloseIdle uint16 = 0x0A
- FeedbagBuddyPrefsAcceptCustomBart uint16 = 0x0B
- FeedbagBuddyPrefsAcceptNonListBart uint16 = 0x0C
- FeedbagBuddyPrefsAcceptBgs uint16 = 0x0D
- FeedbagBuddyPrefsAcceptChromes uint16 = 0x0E
- FeedbagBuddyPrefsAcceptBLSounds uint16 = 0x0F
- FeedbagBuddyPrefsAcceptIMSounds uint16 = 0x10
- FeedbagBuddyPrefsNoSeeRecentBuddies uint16 = 0x11
- FeedbagBuddyPrefsAcceptSMSLegal uint16 = 0x12
- FeedbagBuddyPrefsEnterDoesCRLF uint16 = 0x14
- FeedbagBuddyPrefsPlayIMSound uint16 = 0x15
- FeedbagBuddyPrefsDiscloseTyping uint16 = 0x16
- FeedbagBuddyPrefsAcceptSuperIcons uint16 = 0x18
- FeedbagBuddyPrefsAcceptBLRichText uint16 = 0x19
- FeedbagBuddyPrefsReduceIMSound uint16 = 0x1A
- FeedbagBuddyPrefsConfirmDirectIM uint16 = 0x1B
- FeedbagBuddyPrefsOneTabbedIMWindow uint16 = 0x1C
- FeedbagBuddyPrefsBuddyInfoOnMouseover uint16 = 0x1D
- FeedbagBuddyPrefsDiscloseBuddyMatches uint16 = 0x1E
- FeedbagBuddyPrefsCatchIMs uint16 = 0x1F
- FeedbagBuddyPrefsShowFriendlyName uint16 = 0x20
- FeedbagBuddyPrefsDiscloseRadio uint16 = 0x21
- FeedbagBuddyPrefsShowCapabilities uint16 = 0x22
- FeedbagBuddyPrefsShowBuddyListFilter uint16 = 0x23
- FeedbagBuddyPrefsShowAwayIdle uint16 = 0x24
- FeedbagBuddyPrefsShowMobile uint16 = 0x25
- FeedbagBuddyPrefsSortBuddyList uint16 = 0x26
- FeedbagBuddyPrefsCatchIMsForClient uint16 = 0x27
- FeedbagBuddyPrefsNewMessageSmallNotify uint16 = 0x28
- FeedbagBuddyPrefsNoFrequentBuddies uint16 = 0x29
- FeedbagBuddyPrefsBlogAwayMessages uint16 = 0x2A
- FeedbagBuddyPrefsBlogAIMSigMessages uint16 = 0x2B
- FeedbagBuddyPrefsBlogNoComments uint16 = 0x2C
- FeedbagBuddyPrefsFriendOfFriend uint16 = 0x2D
- FeedbagBuddyPrefsFriendGetContactList uint16 = 0x2E
- FeedbagBuddyPrefsCompadInit uint16 = 0x2F
- FeedbagBuddyPrefsSendBuddyFeed uint16 = 0x30
- FeedbagBuddyPrefsBlkSendIMWhileAway uint16 = 0x31
- FeedbagBuddyPrefsShowBuddyFeed uint16 = 0x32
- FeedbagBuddyPrefsNoSaveVanityInfo uint16 = 0x33
- FeedbagBuddyPrefsAcceptOfflineIM uint16 = 0x34
- FeedbagBuddyPrefsShowGroups uint16 = 0x35
- FeedbagBuddyPrefsSortGroup uint16 = 0x36
- FeedbagBuddyPrefsShowOfflineBuddies uint16 = 0x37
- FeedbagBuddyPrefsExpandBuddies uint16 = 0x38
- FeedbagBuddyPrefsThirdPartyFeeds uint16 = 0x39
- FeedbagBuddyPrefsNotifyReceivedInvite uint16 = 0x3A
- FeedbagBuddyPrefsApfAutoAccept uint16 = 0x3B
- FeedbagBuddyPrefsApfAutoAcceptBuddy uint16 = 0x3C
- FeedbagBuddyPrefsBlockAwayMsgFeed uint16 = 0x3D
- FeedbagBuddyPrefsBlockAIMProfileFeed uint16 = 0x3E
- FeedbagBuddyPrefsBlockAIMPagesFeed uint16 = 0x3F
- FeedbagBuddyPrefsBlockJournalsFeed uint16 = 0x40
- FeedbagBuddyPrefsBlockLocationFeed uint16 = 0x41
- FeedbagBuddyPrefsBlockStickiesFeed uint16 = 0x42
- FeedbagBuddyPrefsBlockUncutFeed uint16 = 0x43
- FeedbagBuddyPrefsBlockLinksFeed uint16 = 0x44
- FeedbagBuddyPrefsBlockAIMBulletinFeed uint16 = 0x45
- FeedbagBuddyPrefsSaveStatusMsg uint16 = 0x46
- FeedbagBuddyPrefsApfNotifyReceivedByEmail uint16 = 0x47
- FeedbagBuddyPrefsShowOfflineGrp uint16 = 0x48
- FeedbagBuddyPrefsOfflineGrpCollapsed uint16 = 0x49
- FeedbagBuddyPrefsFirstIMSoundOnly uint16 = 0x4A
- FeedbagBuddyPrefsImblastInviteNotify uint16 = 0x4B
- )
- // Web-client-only preferences with no OSCAR equivalent. They are persisted in
- // the buddy-prefs bitmask at positions above OSCAR's range (0x4B); no real
- // OSCAR client reads or writes these bits.
- const (
- FeedbagBuddyPrefsViewIMsInBubbles uint16 = 0x4C
- FeedbagBuddyPrefsViewIMTimestampsRelative uint16 = 0x4D
- FeedbagBuddyPrefsGlobalOTR uint16 = 0x4E
- FeedbagBuddyPrefsImblastInviteFromBuddyOnly uint16 = 0x4F
- )
- // buddyPrefTags returns the (valid, value) TLV tags that hold prefNum.
- func buddyPrefTags(prefNum uint16) (validTag, valueTag uint16) {
- if prefNum < 32 {
- return FeedbagAttributesBuddyPrefsValid, FeedbagAttributesBuddyPrefs
- }
- return FeedbagAttributesBuddyPrefs2Valid, FeedbagAttributesBuddyPrefs2
- }
- // buddyPrefBit returns the byte index and bit mask for prefNum within a bitmask
- // of the given byte length.
- func buddyPrefBit(prefNum uint16, length int) (index int, mask byte) {
- if prefNum < 32 {
- // most significant bit on the right side
- index = (length - 1) - int(prefNum)/8
- return index, byte(1) << (prefNum % 8)
- }
- // most significant bit on the left side
- offset := 0
- if prefNum != 32 {
- offset = int(prefNum) - 33
- }
- index = offset / 8
- return index, byte(0x80) >> (offset % 8)
- }
- // buddyPrefDefaults is the source of truth for the default value of each buddy
- // preference: the value BuddyPref returns when the preference bit is absent from
- // the feedbag bitmask.
- var buddyPrefDefaults = map[uint16]bool{
- FeedbagBuddyPrefsDisplayLogin: true,
- FeedbagBuddyPrefsDisplayEBuddy: true,
- FeedbagBuddyPrefsPlayEnter: true,
- FeedbagBuddyPrefsPlayExit: true,
- FeedbagBuddyPrefsViewIMStamp: true,
- FeedbagBuddyPrefsViewSmileys: true,
- FeedbagBuddyPrefsAcceptIcons: true,
- FeedbagBuddyPrefsKnockNonAOLIMs: true,
- FeedbagBuddyPrefsKnockNonListIMs: true,
- FeedbagBuddyPrefsDiscloseIdle: true,
- FeedbagBuddyPrefsAcceptCustomBart: false,
- FeedbagBuddyPrefsAcceptNonListBart: false,
- FeedbagBuddyPrefsAcceptBgs: true,
- FeedbagBuddyPrefsAcceptChromes: true,
- FeedbagBuddyPrefsAcceptBLSounds: true,
- FeedbagBuddyPrefsAcceptIMSounds: true,
- FeedbagBuddyPrefsNoSeeRecentBuddies: false,
- FeedbagBuddyPrefsAcceptSMSLegal: false,
- FeedbagBuddyPrefsEnterDoesCRLF: false,
- FeedbagBuddyPrefsPlayIMSound: true,
- FeedbagBuddyPrefsDiscloseTyping: true,
- FeedbagBuddyPrefsAcceptSuperIcons: true,
- FeedbagBuddyPrefsAcceptBLRichText: true,
- FeedbagBuddyPrefsReduceIMSound: true,
- FeedbagBuddyPrefsConfirmDirectIM: true,
- FeedbagBuddyPrefsOneTabbedIMWindow: true,
- FeedbagBuddyPrefsBuddyInfoOnMouseover: true,
- FeedbagBuddyPrefsDiscloseBuddyMatches: true,
- FeedbagBuddyPrefsCatchIMs: false,
- FeedbagBuddyPrefsShowFriendlyName: true,
- FeedbagBuddyPrefsDiscloseRadio: true,
- FeedbagBuddyPrefsShowCapabilities: true,
- FeedbagBuddyPrefsShowBuddyListFilter: true,
- FeedbagBuddyPrefsShowAwayIdle: true,
- FeedbagBuddyPrefsShowMobile: true,
- FeedbagBuddyPrefsSortBuddyList: false,
- FeedbagBuddyPrefsCatchIMsForClient: false,
- FeedbagBuddyPrefsNewMessageSmallNotify: true,
- FeedbagBuddyPrefsNoFrequentBuddies: false,
- FeedbagBuddyPrefsBlogAwayMessages: false,
- FeedbagBuddyPrefsBlogAIMSigMessages: false,
- FeedbagBuddyPrefsBlogNoComments: false,
- FeedbagBuddyPrefsFriendOfFriend: false,
- FeedbagBuddyPrefsFriendGetContactList: false,
- FeedbagBuddyPrefsCompadInit: false,
- FeedbagBuddyPrefsSendBuddyFeed: true,
- FeedbagBuddyPrefsBlkSendIMWhileAway: false,
- FeedbagBuddyPrefsShowBuddyFeed: true,
- FeedbagBuddyPrefsNoSaveVanityInfo: false,
- FeedbagBuddyPrefsAcceptOfflineIM: true,
- // ShowGroups deliberately departs from the OSCAR spec default (0). The web
- // client keys group-header visibility off this pref and has no default of
- // its own, so we default it on to keep headers visible.
- FeedbagBuddyPrefsShowGroups: true,
- FeedbagBuddyPrefsSortGroup: true,
- FeedbagBuddyPrefsShowOfflineBuddies: true,
- FeedbagBuddyPrefsExpandBuddies: false,
- FeedbagBuddyPrefsThirdPartyFeeds: false,
- FeedbagBuddyPrefsNotifyReceivedInvite: true,
- FeedbagBuddyPrefsApfAutoAccept: false,
- FeedbagBuddyPrefsApfAutoAcceptBuddy: false,
- FeedbagBuddyPrefsBlockAwayMsgFeed: false,
- FeedbagBuddyPrefsBlockAIMProfileFeed: false,
- FeedbagBuddyPrefsBlockAIMPagesFeed: false,
- FeedbagBuddyPrefsBlockJournalsFeed: false,
- FeedbagBuddyPrefsBlockLocationFeed: false,
- FeedbagBuddyPrefsBlockStickiesFeed: false,
- FeedbagBuddyPrefsBlockUncutFeed: false,
- FeedbagBuddyPrefsBlockLinksFeed: false,
- FeedbagBuddyPrefsBlockAIMBulletinFeed: false,
- FeedbagBuddyPrefsSaveStatusMsg: true,
- FeedbagBuddyPrefsApfNotifyReceivedByEmail: false,
- FeedbagBuddyPrefsShowOfflineGrp: true,
- FeedbagBuddyPrefsOfflineGrpCollapsed: false,
- FeedbagBuddyPrefsFirstIMSoundOnly: false,
- FeedbagBuddyPrefsImblastInviteNotify: true,
- // Web-client-only preferences with no OSCAR equivalent.
- FeedbagBuddyPrefsViewIMsInBubbles: true,
- FeedbagBuddyPrefsViewIMTimestampsRelative: false,
- FeedbagBuddyPrefsGlobalOTR: false,
- FeedbagBuddyPrefsImblastInviteFromBuddyOnly: false,
- }
- // BuddyPref returns the effective boolean value of preference prefNum in a
- // feedbag buddy-prefs TLV list: the stored value when the preference is present
- // in the bitmask, otherwise the preference's default (see buddyPrefDefaults).
- // Whether the preference is actually present ("valid") is an internal detail;
- // BuddyPref is the source of truth for default values.
- func BuddyPref(list TLVList, prefNum uint16) bool {
- if valid, value := readBuddyPref(list, prefNum); valid {
- return value
- }
- return buddyPrefDefaults[prefNum]
- }
- // readBuddyPref reads preference prefNum from a feedbag buddy-prefs TLV list.
- // valid reports whether the preference is present in the bitmask; value is its
- // boolean value, meaningful only when valid is true.
- func readBuddyPref(list TLVList, prefNum uint16) (valid, value bool) {
- validTag, valueTag := buddyPrefTags(prefNum)
- validBytes, ok := list.Bytes(validTag)
- if !ok {
- return false, false
- }
- valueBytes, ok := list.Bytes(valueTag)
- if !ok {
- return false, false
- }
- index, mask := buddyPrefBit(prefNum, len(validBytes))
- if index < 0 || index >= len(validBytes) || index >= len(valueBytes) {
- return false, false
- }
- valid = validBytes[index]&mask != 0
- value = valueBytes[index]&mask != 0
- return valid, value
- }
- // SetBuddyPref returns list with preference prefNum set to value and marked
- // valid, growing the underlying bitmask byte slices as needed. The value and
- // valid TLVs are created if absent and other preference bits are preserved.
- func SetBuddyPref(list TLVList, prefNum uint16, value bool) TLVList {
- validTag, valueTag := buddyPrefTags(prefNum)
- // Clone so we never mutate a caller's backing array in place.
- rawValid, _ := list.Bytes(validTag)
- validBytes := slices.Clone(rawValid)
- rawValue, _ := list.Bytes(valueTag)
- valueBytes := slices.Clone(rawValue)
- // Normalize both bitmasks to a common length large enough to hold prefNum.
- var length int
- if prefNum < 32 {
- length = max(4, len(validBytes), len(valueBytes))
- validBytes = leftPad(validBytes, length)
- valueBytes = leftPad(valueBytes, length)
- } else {
- offset := 0
- if prefNum != 32 {
- offset = int(prefNum) - 33
- }
- length = max(offset/8+1, len(validBytes), len(valueBytes))
- validBytes = rightPad(validBytes, length)
- valueBytes = rightPad(valueBytes, length)
- }
- index, mask := buddyPrefBit(prefNum, length)
- validBytes[index] |= mask // a preference we set is always valid
- if value {
- valueBytes[index] |= mask
- } else {
- valueBytes[index] &^= mask
- }
- list.Set(NewTLVBE(validTag, validBytes))
- list.Set(NewTLVBE(valueTag, valueBytes))
- return list
- }
- // leftPad grows b to length n by prepending zero bytes (bit indexing is
- // right-aligned for the fixed BuddyPrefs bitmask).
- func leftPad(b []byte, n int) []byte {
- if len(b) >= n {
- return b
- }
- return append(make([]byte, n-len(b)), b...)
- }
- // rightPad grows b to length n by appending zero bytes (bit indexing is
- // left-aligned for the positional BuddyPrefs2 bitmask).
- func rightPad(b []byte, n int) []byte {
- if len(b) >= n {
- return b
- }
- return append(b, make([]byte, n-len(b))...)
- }
|