feedbag.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. package server
  2. import (
  3. "errors"
  4. "io"
  5. "time"
  6. "github.com/mkaminski/goaim/oscar"
  7. )
  8. type FeedbagHandler interface {
  9. DeleteItemHandler(sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error)
  10. InsertItemHandler(sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error)
  11. QueryHandler(sess *Session, fm FeedbagManager) (XMessage, error)
  12. QueryIfModifiedHandler(sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error)
  13. RightsQueryHandler() XMessage
  14. StartClusterHandler(oscar.SNAC_0x13_0x11_FeedbagStartCluster)
  15. UpdateItemHandler(sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error)
  16. }
  17. func NewFeedbagRouter() FeedbagRouter {
  18. return FeedbagRouter{
  19. FeedbagHandler: FeedbagService{},
  20. }
  21. }
  22. type FeedbagRouter struct {
  23. FeedbagHandler
  24. }
  25. func (rt FeedbagRouter) RouteFeedbag(sm SessionManager, sess *Session, fm FeedbagManager, snac oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
  26. switch snac.SubGroup {
  27. case oscar.FeedbagRightsQuery:
  28. inSNAC := oscar.SNAC_0x13_0x02_FeedbagRightsQuery{}
  29. if err := oscar.Unmarshal(&inSNAC, r); err != nil {
  30. return err
  31. }
  32. outSNAC := rt.RightsQueryHandler()
  33. return writeOutSNAC(snac, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
  34. case oscar.FeedbagQuery:
  35. inSNAC, err := rt.QueryHandler(sess, fm)
  36. if err != nil {
  37. return err
  38. }
  39. return writeOutSNAC(snac, inSNAC.snacFrame, inSNAC.snacOut, sequence, w)
  40. case oscar.FeedbagQueryIfModified:
  41. inSNAC := oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{}
  42. if err := oscar.Unmarshal(&inSNAC, r); err != nil {
  43. return err
  44. }
  45. outSNAC, err := rt.QueryIfModifiedHandler(sess, fm, inSNAC)
  46. if err != nil {
  47. return err
  48. }
  49. return writeOutSNAC(snac, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
  50. case oscar.FeedbagUse:
  51. return nil
  52. case oscar.FeedbagInsertItem:
  53. inSNAC := oscar.SNAC_0x13_0x08_FeedbagInsertItem{}
  54. if err := oscar.Unmarshal(&inSNAC, r); err != nil {
  55. return err
  56. }
  57. outSNAC, err := rt.InsertItemHandler(sm, sess, fm, inSNAC)
  58. if err != nil {
  59. return err
  60. }
  61. return writeOutSNAC(snac, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
  62. case oscar.FeedbagUpdateItem:
  63. inSNAC := oscar.SNAC_0x13_0x09_FeedbagUpdateItem{}
  64. if err := oscar.Unmarshal(&inSNAC, r); err != nil {
  65. return err
  66. }
  67. outSNAC, err := rt.UpdateItemHandler(sm, sess, fm, inSNAC)
  68. if err != nil {
  69. return err
  70. }
  71. return writeOutSNAC(snac, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
  72. case oscar.FeedbagDeleteItem:
  73. inSNAC := oscar.SNAC_0x13_0x0A_FeedbagDeleteItem{}
  74. if err := oscar.Unmarshal(&inSNAC, r); err != nil {
  75. return err
  76. }
  77. outSNAC, err := rt.DeleteItemHandler(sm, sess, fm, inSNAC)
  78. if err != nil {
  79. return err
  80. }
  81. return writeOutSNAC(snac, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
  82. case oscar.FeedbagStartCluster:
  83. inSNAC := oscar.SNAC_0x13_0x11_FeedbagStartCluster{}
  84. if err := oscar.Unmarshal(&inSNAC, r); err != nil {
  85. return err
  86. }
  87. rt.StartClusterHandler(inSNAC)
  88. return nil
  89. case oscar.FeedbagEndCluster:
  90. return nil
  91. default:
  92. return ErrUnsupportedSubGroup
  93. }
  94. }
  95. type FeedbagService struct {
  96. }
  97. func (s FeedbagService) RightsQueryHandler() XMessage {
  98. return XMessage{
  99. snacFrame: oscar.SnacFrame{
  100. FoodGroup: oscar.FEEDBAG,
  101. SubGroup: oscar.FeedbagRightsReply,
  102. },
  103. snacOut: oscar.SNAC_0x13_0x03_FeedbagRightsReply{
  104. TLVRestBlock: oscar.TLVRestBlock{
  105. TLVList: oscar.TLVList{
  106. oscar.NewTLV(0x03, uint16(200)),
  107. oscar.NewTLV(0x04, []uint16{
  108. 0x3D,
  109. 0x3D,
  110. 0x64,
  111. 0x64,
  112. 0x01,
  113. 0x01,
  114. 0x32,
  115. 0x00,
  116. 0x00,
  117. 0x03,
  118. 0x00,
  119. 0x00,
  120. 0x00,
  121. 0x80,
  122. 0xFF,
  123. 0x14,
  124. 0xC8,
  125. 0x01,
  126. 0x00,
  127. 0x01,
  128. 0x00,
  129. }),
  130. oscar.NewTLV(0x05, uint16(200)),
  131. oscar.NewTLV(0x06, uint16(200)),
  132. oscar.NewTLV(0x07, uint16(200)),
  133. oscar.NewTLV(0x08, uint16(200)),
  134. oscar.NewTLV(0x09, uint16(200)),
  135. oscar.NewTLV(0x0A, uint16(200)),
  136. oscar.NewTLV(0x0C, uint16(200)),
  137. oscar.NewTLV(0x0D, uint16(200)),
  138. oscar.NewTLV(0x0E, uint16(100)),
  139. },
  140. },
  141. },
  142. }
  143. }
  144. func (s FeedbagService) QueryHandler(sess *Session, fm FeedbagManager) (XMessage, error) {
  145. fb, err := fm.Retrieve(sess.ScreenName)
  146. if err != nil {
  147. return XMessage{}, err
  148. }
  149. lm := time.UnixMilli(0)
  150. if len(fb) > 0 {
  151. lm, err = fm.LastModified(sess.ScreenName)
  152. if err != nil {
  153. return XMessage{}, err
  154. }
  155. }
  156. return XMessage{
  157. snacFrame: oscar.SnacFrame{
  158. FoodGroup: oscar.FEEDBAG,
  159. SubGroup: oscar.FeedbagReply,
  160. },
  161. snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
  162. Version: 0,
  163. Items: fb,
  164. LastUpdate: uint32(lm.Unix()),
  165. },
  166. }, nil
  167. }
  168. func (s FeedbagService) QueryIfModifiedHandler(sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error) {
  169. fb, err := fm.Retrieve(sess.ScreenName)
  170. if err != nil {
  171. return XMessage{}, err
  172. }
  173. lm := time.UnixMilli(0)
  174. if len(fb) > 0 {
  175. lm, err = fm.LastModified(sess.ScreenName)
  176. if err != nil {
  177. return XMessage{}, err
  178. }
  179. if lm.Before(time.Unix(int64(snacPayloadIn.LastUpdate), 0)) {
  180. return XMessage{
  181. snacFrame: oscar.SnacFrame{
  182. FoodGroup: oscar.FEEDBAG,
  183. SubGroup: oscar.FeedbagReplyNotModified,
  184. },
  185. snacOut: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
  186. LastUpdate: uint32(lm.Unix()),
  187. Count: uint8(len(fb)),
  188. },
  189. }, nil
  190. }
  191. }
  192. return XMessage{
  193. snacFrame: oscar.SnacFrame{
  194. FoodGroup: oscar.FEEDBAG,
  195. SubGroup: oscar.FeedbagReply,
  196. },
  197. snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
  198. Version: 0,
  199. Items: fb,
  200. LastUpdate: uint32(lm.Unix()),
  201. },
  202. }, nil
  203. }
  204. func (s FeedbagService) InsertItemHandler(sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error) {
  205. for _, item := range snacPayloadIn.Items {
  206. // don't let users block themselves, it causes the AIM client to go
  207. // into a weird state.
  208. if item.ClassID == 3 && item.Name == sess.ScreenName {
  209. return XMessage{
  210. snacFrame: oscar.SnacFrame{
  211. FoodGroup: oscar.FEEDBAG,
  212. SubGroup: oscar.FeedbagErr,
  213. },
  214. snacOut: oscar.SnacError{
  215. Code: ErrorCodeNotSupportedByHost,
  216. },
  217. }, nil
  218. }
  219. }
  220. if err := fm.Upsert(sess.ScreenName, snacPayloadIn.Items); err != nil {
  221. return XMessage{}, nil
  222. }
  223. for _, item := range snacPayloadIn.Items {
  224. switch item.ClassID {
  225. case oscar.FeedbagClassIdBuddy, oscar.FeedbagClassIDPermit: // add new buddy
  226. err := UnicastArrival(item.Name, sess.ScreenName, sm)
  227. switch {
  228. case errors.Is(err, ErrSessNotFound):
  229. continue
  230. case err != nil:
  231. return XMessage{}, err
  232. }
  233. case oscar.FeedbagClassIDDeny: // block buddy
  234. // notify this user that buddy is offline
  235. err := UnicastDeparture(item.Name, sess.ScreenName, sm)
  236. switch {
  237. case errors.Is(err, ErrSessNotFound):
  238. continue
  239. case err != nil:
  240. return XMessage{}, err
  241. }
  242. // notify former buddy that this user is offline
  243. if err := UnicastDeparture(sess.ScreenName, item.Name, sm); err != nil {
  244. return XMessage{}, err
  245. }
  246. }
  247. }
  248. snacPayloadOut := oscar.SNAC_0x13_0x0E_FeedbagStatus{}
  249. for range snacPayloadIn.Items {
  250. snacPayloadOut.Results = append(snacPayloadOut.Results, 0x0000)
  251. }
  252. return XMessage{
  253. snacFrame: oscar.SnacFrame{
  254. FoodGroup: oscar.FEEDBAG,
  255. SubGroup: oscar.FeedbagStatus,
  256. },
  257. snacOut: snacPayloadOut,
  258. }, nil
  259. }
  260. func (s FeedbagService) UpdateItemHandler(sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error) {
  261. if err := fm.Upsert(sess.ScreenName, snacPayloadIn.Items); err != nil {
  262. return XMessage{}, nil
  263. }
  264. for _, item := range snacPayloadIn.Items {
  265. switch item.ClassID {
  266. case oscar.FeedbagClassIdBuddy, oscar.FeedbagClassIDPermit:
  267. err := UnicastArrival(item.Name, sess.ScreenName, sm)
  268. switch {
  269. case errors.Is(err, ErrSessNotFound):
  270. continue
  271. case err != nil:
  272. return XMessage{}, err
  273. }
  274. }
  275. }
  276. snacPayloadOut := oscar.SNAC_0x13_0x0E_FeedbagStatus{}
  277. for range snacPayloadIn.Items {
  278. snacPayloadOut.Results = append(snacPayloadOut.Results, 0x0000)
  279. }
  280. return XMessage{
  281. snacFrame: oscar.SnacFrame{
  282. FoodGroup: oscar.FEEDBAG,
  283. SubGroup: oscar.FeedbagStatus,
  284. },
  285. snacOut: snacPayloadOut,
  286. }, nil
  287. }
  288. func (s FeedbagService) DeleteItemHandler(sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error) {
  289. if err := fm.Delete(sess.ScreenName, snacPayloadIn.Items); err != nil {
  290. return XMessage{}, err
  291. }
  292. for _, item := range snacPayloadIn.Items {
  293. if item.ClassID == oscar.FeedbagClassIDDeny {
  294. err := UnicastArrival(item.Name, sess.ScreenName, sm)
  295. switch {
  296. case errors.Is(err, ErrSessNotFound):
  297. continue
  298. case err != nil:
  299. return XMessage{}, err
  300. }
  301. err = UnicastArrival(sess.ScreenName, item.Name, sm)
  302. switch {
  303. case errors.Is(err, ErrSessNotFound):
  304. continue
  305. case err != nil:
  306. return XMessage{}, err
  307. }
  308. }
  309. }
  310. snacPayloadOut := oscar.SNAC_0x13_0x0E_FeedbagStatus{}
  311. for range snacPayloadIn.Items {
  312. snacPayloadOut.Results = append(snacPayloadOut.Results, 0x0000) // success by default
  313. }
  314. return XMessage{
  315. snacFrame: oscar.SnacFrame{
  316. FoodGroup: oscar.FEEDBAG,
  317. SubGroup: oscar.FeedbagStatus,
  318. },
  319. snacOut: snacPayloadOut,
  320. }, nil
  321. }
  322. // StartClusterHandler exists to capture the SNAC input in unit tests to verify
  323. // it's correctly unmarshalled.
  324. func (s FeedbagService) StartClusterHandler(oscar.SNAC_0x13_0x11_FeedbagStartCluster) {
  325. }