bart.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package foodgroup
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/md5"
  6. "errors"
  7. "fmt"
  8. "log/slog"
  9. "github.com/mk6i/open-oscar-server/state"
  10. "github.com/mk6i/open-oscar-server/wire"
  11. )
  12. // blankGIF is a blank, transparent 50x50p GIF that takes the place of a
  13. // cleared buddy icon.
  14. var blankGIF = []byte{
  15. 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x32, 0x00, 0x32, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
  16. 0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00,
  17. 0x32, 0x00, 0x32, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b,
  18. }
  19. func NewBARTService(
  20. logger *slog.Logger,
  21. bartItemManager BARTItemManager,
  22. messageRelayer MessageRelayer,
  23. relationshipFetcher RelationshipFetcher,
  24. sessionRetriever SessionRetriever,
  25. ) BARTService {
  26. return BARTService{
  27. bartItemManager: bartItemManager,
  28. buddyUpdateBroadcaster: newBuddyNotifier(bartItemManager, relationshipFetcher, messageRelayer, sessionRetriever),
  29. messageRelayer: messageRelayer,
  30. logger: logger,
  31. }
  32. }
  33. type BARTService struct {
  34. bartItemManager BARTItemManager
  35. buddyUpdateBroadcaster buddyBroadcaster
  36. messageRelayer MessageRelayer
  37. logger *slog.Logger
  38. }
  39. func (s BARTService) UpsertItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x10_0x02_BARTUploadQuery) (wire.SNACMessage, error) {
  40. h := md5.New()
  41. if _, err := h.Write(inBody.Data); err != nil {
  42. return wire.SNACMessage{}, err
  43. }
  44. hash := h.Sum(nil)
  45. if err := s.bartItemManager.InsertBARTItem(ctx, hash, inBody.Data, inBody.Type); err != nil {
  46. if !errors.Is(err, state.ErrBARTItemExists) {
  47. return wire.SNACMessage{}, fmt.Errorf("failed to insert BART item: %w", err)
  48. }
  49. }
  50. s.logger.DebugContext(ctx, "successfully uploaded BART item", "hash", fmt.Sprintf("%x", hash))
  51. bartID, hasIcon := instance.Session().BuddyIcon()
  52. if hasIcon && bytes.Equal(hash, bartID.Hash) {
  53. // unset unknown flag
  54. bartID.Flags &^= wire.BARTFlagsUnknown
  55. instance.Session().SetBuddyIcon(bartID)
  56. sendUserInfoUpdateToAll(ctx, s.messageRelayer, instance)
  57. if err := s.buddyUpdateBroadcaster.BroadcastBuddyArrived(ctx, instance.IdentScreenName(), instance.Session().TLVUserInfo()); err != nil {
  58. return wire.SNACMessage{}, err
  59. }
  60. } else {
  61. bartID = wire.BARTID{
  62. Type: inBody.Type,
  63. BARTInfo: wire.BARTInfo{
  64. Flags: wire.BARTFlagsCustom,
  65. Hash: hash,
  66. },
  67. }
  68. }
  69. return wire.SNACMessage{
  70. Frame: wire.SNACFrame{
  71. FoodGroup: wire.BART,
  72. SubGroup: wire.BARTUploadReply,
  73. RequestID: inFrame.RequestID,
  74. },
  75. Body: wire.SNAC_0x10_0x03_BARTUploadReply{
  76. Code: wire.BARTReplyCodesSuccess,
  77. ID: bartID,
  78. },
  79. }, nil
  80. }
  81. // RetrieveItem fetches a BART item from the data store. The item is selected
  82. // based on inBody.Hash. It's unclear what effect inBody.Flags is supposed to
  83. // have on the request.
  84. func (s BARTService) RetrieveItem(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x10_0x04_BARTDownloadQuery) (wire.SNACMessage, error) {
  85. var item []byte
  86. if inBody.HasClearIconHash() {
  87. item = blankGIF
  88. } else {
  89. var err error
  90. if item, err = s.bartItemManager.BARTItem(ctx, inBody.Hash); err != nil {
  91. return wire.SNACMessage{}, err
  92. }
  93. }
  94. // todo... how to reply if requested item doesn't exist
  95. return wire.SNACMessage{
  96. Frame: wire.SNACFrame{
  97. FoodGroup: wire.BART,
  98. SubGroup: wire.BARTDownloadReply,
  99. RequestID: inFrame.RequestID,
  100. },
  101. Body: wire.SNAC_0x10_0x05_BARTDownloadReply{
  102. ScreenName: inBody.ScreenName,
  103. BARTID: inBody.BARTID,
  104. Data: item,
  105. },
  106. }, nil
  107. }
  108. // RetrieveItemV2 implements the SNAC handler for SNAC(0x10,0x06) BARTDownload2Query.
  109. func (s BARTService) RetrieveItemV2(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x10_0x06_BARTDownload2Query) ([]wire.SNACMessage, error) {
  110. var result []wire.SNACMessage
  111. for _, id := range inBody.IDs {
  112. var item []byte
  113. if id.Type == wire.BARTTypesBuddyIcon && id.HasClearIconHash() {
  114. item = blankGIF
  115. } else {
  116. var err error
  117. if item, err = s.bartItemManager.BARTItem(ctx, id.Hash); err != nil {
  118. return nil, err
  119. }
  120. }
  121. result = append(result, wire.SNACMessage{
  122. Frame: wire.SNACFrame{
  123. FoodGroup: wire.BART,
  124. SubGroup: wire.BARTDownload2Reply,
  125. RequestID: inFrame.RequestID,
  126. },
  127. Body: wire.SNAC_0x10_0x07_BARTDownload2Reply{
  128. ScreenName: inBody.ScreenName,
  129. ReplyID: wire.BartQueryReplyID{
  130. QueryID: id,
  131. Code: 0x00, // found
  132. ReplyID: wire.BARTID{
  133. Type: id.Type,
  134. BARTInfo: id.BARTInfo,
  135. },
  136. },
  137. Data: item,
  138. },
  139. })
  140. }
  141. return result, nil
  142. }