oservice.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. package handler
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "time"
  8. "github.com/mkaminski/goaim/oscar"
  9. "github.com/mkaminski/goaim/server"
  10. "github.com/mkaminski/goaim/state"
  11. )
  12. // NewOServiceService creates a new instance of OServiceService.
  13. func NewOServiceService(cfg server.Config, messageRelayer MessageRelayer, feedbagManager FeedbagManager) *OServiceService {
  14. return &OServiceService{cfg: cfg, messageRelayer: messageRelayer, feedbagManager: feedbagManager}
  15. }
  16. // OServiceService contains handlers for the OService food group.
  17. type OServiceService struct {
  18. cfg server.Config
  19. feedbagManager FeedbagManager
  20. messageRelayer MessageRelayer
  21. }
  22. // ClientVersionsHandler informs the server what food group versions the client
  23. // supports and returns to the client what food group versions it supports.
  24. // This method simply regurgitates versions supplied by the client in inBody
  25. // back to the client in a OServiceHostVersions SNAC. The server doesn't
  26. // attempt to accommodate any particular food group version. The server
  27. // implicitly accommodates any food group version for Windows AIM clients 5.x.
  28. // It returns SNAC oscar.OServiceHostVersions containing the server's supported
  29. // food group versions.
  30. func (s OServiceService) ClientVersionsHandler(_ context.Context, frame oscar.SNACFrame, inBody oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.SNACMessage {
  31. return oscar.SNACMessage{
  32. Frame: oscar.SNACFrame{
  33. FoodGroup: oscar.OService,
  34. SubGroup: oscar.OServiceHostVersions,
  35. RequestID: frame.RequestID,
  36. },
  37. Body: oscar.SNAC_0x01_0x18_OServiceHostVersions{
  38. Versions: inBody.Versions,
  39. },
  40. }
  41. }
  42. // RateParamsQueryHandler returns SNAC rate limits.
  43. // The purpose of this method is to provide information about rate limits that
  44. // can be enforced on the server side. The response consists of two main parts:
  45. // rate classes and rate groups. Rate classes define limits based on specific
  46. // parameters, while rate groups associate these limits with relevant SNAC
  47. // types.
  48. //
  49. // Note: The current implementation does not enforce server-side rate limiting.
  50. // Instead, the provided values inform the client about the recommended
  51. // client-side rate limits.
  52. //
  53. // It returns SNAC osca.rOServiceRateParamsReply containing rate limits for
  54. // sending Instant Messages (IMs) and chat messages. More refined limits may be
  55. // added in the future if/when server rate limiting is implemented.
  56. func (s OServiceService) RateParamsQueryHandler(_ context.Context, inFrame oscar.SNACFrame) oscar.SNACMessage {
  57. return oscar.SNACMessage{
  58. Frame: oscar.SNACFrame{
  59. FoodGroup: oscar.OService,
  60. SubGroup: oscar.OServiceRateParamsReply,
  61. RequestID: inFrame.RequestID,
  62. },
  63. Body: oscar.SNAC_0x01_0x07_OServiceRateParamsReply{
  64. RateClasses: []struct {
  65. ID uint16
  66. WindowSize uint32
  67. ClearLevel uint32
  68. AlertLevel uint32
  69. LimitLevel uint32
  70. DisconnectLevel uint32
  71. CurrentLevel uint32
  72. MaxLevel uint32
  73. LastTime uint32
  74. CurrentState uint8
  75. }{
  76. {
  77. ID: 0x01,
  78. WindowSize: 0x0050,
  79. ClearLevel: 0x09C4,
  80. AlertLevel: 0x07D0,
  81. LimitLevel: 0x05DC,
  82. DisconnectLevel: 0x0320,
  83. CurrentLevel: 0x0D69,
  84. MaxLevel: 0x1770,
  85. LastTime: 0x0000,
  86. CurrentState: 0x0,
  87. },
  88. },
  89. RateGroups: []struct {
  90. ID uint16
  91. Pairs []struct {
  92. FoodGroup uint16
  93. SubGroup uint16
  94. } `count_prefix:"uint16"`
  95. }{
  96. {
  97. ID: 1,
  98. Pairs: []struct {
  99. FoodGroup uint16
  100. SubGroup uint16
  101. }{
  102. {
  103. FoodGroup: oscar.Buddy,
  104. SubGroup: oscar.BuddyRightsQuery,
  105. },
  106. {
  107. FoodGroup: oscar.Chat,
  108. SubGroup: oscar.ChatChannelMsgToHost,
  109. },
  110. {
  111. FoodGroup: oscar.ChatNav,
  112. SubGroup: oscar.ChatNavRequestChatRights,
  113. },
  114. {
  115. FoodGroup: oscar.ChatNav,
  116. SubGroup: oscar.ChatNavRequestRoomInfo,
  117. },
  118. {
  119. FoodGroup: oscar.ChatNav,
  120. SubGroup: oscar.ChatNavCreateRoom,
  121. },
  122. {
  123. FoodGroup: oscar.Feedbag,
  124. SubGroup: oscar.FeedbagRightsQuery,
  125. },
  126. {
  127. FoodGroup: oscar.Feedbag,
  128. SubGroup: oscar.FeedbagQuery,
  129. },
  130. {
  131. FoodGroup: oscar.Feedbag,
  132. SubGroup: oscar.FeedbagQueryIfModified,
  133. },
  134. {
  135. FoodGroup: oscar.Feedbag,
  136. SubGroup: oscar.FeedbagUse,
  137. },
  138. {
  139. FoodGroup: oscar.Feedbag,
  140. SubGroup: oscar.FeedbagInsertItem,
  141. },
  142. {
  143. FoodGroup: oscar.Feedbag,
  144. SubGroup: oscar.FeedbagUpdateItem,
  145. },
  146. {
  147. FoodGroup: oscar.Feedbag,
  148. SubGroup: oscar.FeedbagDeleteItem,
  149. },
  150. {
  151. FoodGroup: oscar.Feedbag,
  152. SubGroup: oscar.FeedbagStartCluster,
  153. },
  154. {
  155. FoodGroup: oscar.Feedbag,
  156. SubGroup: oscar.FeedbagEndCluster,
  157. },
  158. {
  159. FoodGroup: oscar.ICBM,
  160. SubGroup: oscar.ICBMAddParameters,
  161. },
  162. {
  163. FoodGroup: oscar.ICBM,
  164. SubGroup: oscar.ICBMParameterQuery,
  165. },
  166. {
  167. FoodGroup: oscar.ICBM,
  168. SubGroup: oscar.ICBMChannelMsgToHost,
  169. },
  170. {
  171. FoodGroup: oscar.ICBM,
  172. SubGroup: oscar.ICBMEvilRequest,
  173. },
  174. {
  175. FoodGroup: oscar.ICBM,
  176. SubGroup: oscar.ICBMClientErr,
  177. },
  178. {
  179. FoodGroup: oscar.ICBM,
  180. SubGroup: oscar.ICBMClientEvent,
  181. },
  182. {
  183. FoodGroup: oscar.Locate,
  184. SubGroup: oscar.LocateRightsQuery,
  185. },
  186. {
  187. FoodGroup: oscar.Locate,
  188. SubGroup: oscar.LocateSetInfo,
  189. },
  190. {
  191. FoodGroup: oscar.Locate,
  192. SubGroup: oscar.LocateSetDirInfo,
  193. },
  194. {
  195. FoodGroup: oscar.Locate,
  196. SubGroup: oscar.LocateGetDirInfo,
  197. },
  198. {
  199. FoodGroup: oscar.Locate,
  200. SubGroup: oscar.LocateSetKeywordInfo,
  201. },
  202. {
  203. FoodGroup: oscar.Locate,
  204. SubGroup: oscar.LocateUserInfoQuery2,
  205. },
  206. {
  207. FoodGroup: oscar.OService,
  208. SubGroup: oscar.OServiceServiceRequest,
  209. },
  210. {
  211. FoodGroup: oscar.OService,
  212. SubGroup: oscar.OServiceClientOnline,
  213. },
  214. {
  215. FoodGroup: oscar.OService,
  216. SubGroup: oscar.OServiceRateParamsQuery,
  217. },
  218. {
  219. FoodGroup: oscar.OService,
  220. SubGroup: oscar.OServiceRateParamsSubAdd,
  221. },
  222. {
  223. FoodGroup: oscar.OService,
  224. SubGroup: oscar.OServiceUserInfoQuery,
  225. },
  226. {
  227. FoodGroup: oscar.OService,
  228. SubGroup: oscar.OServiceIdleNotification,
  229. },
  230. {
  231. FoodGroup: oscar.OService,
  232. SubGroup: oscar.OServiceClientVersions,
  233. },
  234. {
  235. FoodGroup: oscar.OService,
  236. SubGroup: oscar.OServiceSetUserInfoFields,
  237. },
  238. },
  239. },
  240. },
  241. },
  242. }
  243. }
  244. // UserInfoQueryHandler returns SNAC oscar.OServiceUserInfoUpdate containing
  245. // the user's info.
  246. func (s OServiceService) UserInfoQueryHandler(_ context.Context, sess *state.Session, inFrame oscar.SNACFrame) oscar.SNACMessage {
  247. return oscar.SNACMessage{
  248. Frame: oscar.SNACFrame{
  249. FoodGroup: oscar.OService,
  250. SubGroup: oscar.OServiceUserInfoUpdate,
  251. RequestID: inFrame.RequestID,
  252. },
  253. Body: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
  254. TLVUserInfo: sess.TLVUserInfo(),
  255. },
  256. }
  257. }
  258. // SetUserInfoFieldsHandler sets the user's visibility status to visible or invisible.
  259. // The visibility status is set according to the inFrame TLV entry under key
  260. // oscar.OServiceUserInfoStatus. If the value is 0x0000, set invisible. If set
  261. // to 0x0100, set invisible. Else, return an error for any other value.
  262. // It returns SNAC oscar.OServiceUserInfoUpdate containing the user's info.
  263. func (s OServiceService) SetUserInfoFieldsHandler(ctx context.Context, sess *state.Session, inFrame oscar.SNACFrame, inBody oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.SNACMessage, error) {
  264. if status, hasStatus := inBody.GetUint32(oscar.OServiceUserInfoStatus); hasStatus {
  265. switch status {
  266. case 0x0000:
  267. sess.SetInvisible(false)
  268. if err := broadcastArrival(ctx, sess, s.messageRelayer, s.feedbagManager); err != nil {
  269. return oscar.SNACMessage{}, err
  270. }
  271. case 0x0100:
  272. sess.SetInvisible(true)
  273. if err := broadcastDeparture(ctx, sess, s.messageRelayer, s.feedbagManager); err != nil {
  274. return oscar.SNACMessage{}, err
  275. }
  276. default:
  277. return oscar.SNACMessage{}, fmt.Errorf("don't know what to do with status %d", status)
  278. }
  279. }
  280. return oscar.SNACMessage{
  281. Frame: oscar.SNACFrame{
  282. FoodGroup: oscar.OService,
  283. SubGroup: oscar.OServiceUserInfoUpdate,
  284. RequestID: inFrame.RequestID,
  285. },
  286. Body: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
  287. TLVUserInfo: sess.TLVUserInfo(),
  288. },
  289. }, nil
  290. }
  291. // IdleNotificationHandler sets the user idle time.
  292. // Set session idle time to the value of bodyIn.IdleTime. Return a user arrival
  293. // message to all users who have this user on their buddy list.
  294. func (s OServiceService) IdleNotificationHandler(ctx context.Context, sess *state.Session, bodyIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
  295. if bodyIn.IdleTime == 0 {
  296. sess.SetActive()
  297. } else {
  298. sess.SetIdle(time.Duration(bodyIn.IdleTime) * time.Second)
  299. }
  300. return broadcastArrival(ctx, sess, s.messageRelayer, s.feedbagManager)
  301. }
  302. // RateParamsSubAddHandler exists to capture the SNAC input in unit tests to
  303. // verify it's correctly unmarshalled.
  304. func (s OServiceService) RateParamsSubAddHandler(context.Context, oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd) {
  305. }
  306. // NewOServiceServiceForBOS creates a new instance of OServiceServiceForBOS.
  307. func NewOServiceServiceForBOS(oserviceService OServiceService, cr *state.ChatRegistry) *OServiceServiceForBOS {
  308. return &OServiceServiceForBOS{
  309. OServiceService: oserviceService,
  310. chatRegistry: cr,
  311. }
  312. }
  313. // OServiceServiceForBOS contains handlers for the OService food group for the
  314. // BOS service.
  315. type OServiceServiceForBOS struct {
  316. OServiceService
  317. chatRegistry *state.ChatRegistry
  318. }
  319. // ServiceRequestHandler configures food group settings for the current user.
  320. // This method only provides services for the Chat food group; return
  321. // server.ErrUnsupportedSubGroup for any other food group. When the chat food
  322. // group is specified in inFrame, add user to the chat room specified by TLV
  323. // 0x01.
  324. // It returns SNAC oscar.OServiceServiceResponse containing metadata the client
  325. // needs to connect to the chat service and join the chat room.
  326. func (s OServiceServiceForBOS) ServiceRequestHandler(_ context.Context, sess *state.Session, inFrame oscar.SNACFrame, inBody oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.SNACMessage, error) {
  327. if inBody.FoodGroup != oscar.Chat {
  328. return oscar.SNACMessage{}, server.ErrUnsupportedSubGroup
  329. }
  330. roomMeta, ok := inBody.GetSlice(0x01)
  331. if !ok {
  332. return oscar.SNACMessage{}, errors.New("missing room info")
  333. }
  334. roomSnac := oscar.SNAC_0x01_0x04_TLVRoomInfo{}
  335. if err := oscar.Unmarshal(&roomSnac, bytes.NewBuffer(roomMeta)); err != nil {
  336. return oscar.SNACMessage{}, err
  337. }
  338. room, chatSessMgr, err := s.chatRegistry.Retrieve(string(roomSnac.Cookie))
  339. if err != nil {
  340. return oscar.SNACMessage{}, server.ErrUnsupportedSubGroup
  341. }
  342. chatSessMgr.(SessionManager).NewSessionWithSN(sess.ID(), sess.ScreenName())
  343. return oscar.SNACMessage{
  344. Frame: oscar.SNACFrame{
  345. FoodGroup: oscar.OService,
  346. SubGroup: oscar.OServiceServiceResponse,
  347. RequestID: inFrame.RequestID,
  348. },
  349. Body: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
  350. TLVRestBlock: oscar.TLVRestBlock{
  351. TLVList: oscar.TLVList{
  352. oscar.NewTLV(oscar.OServiceTLVTagsReconnectHere, server.Address(s.cfg.OSCARHost, s.cfg.ChatPort)),
  353. oscar.NewTLV(oscar.OServiceTLVTagsLoginCookie, server.ChatCookie{
  354. Cookie: []byte(room.Cookie),
  355. SessID: sess.ID(),
  356. }),
  357. oscar.NewTLV(oscar.OServiceTLVTagsGroupID, oscar.Chat),
  358. oscar.NewTLV(oscar.OServiceTLVTagsSSLCertName, ""),
  359. oscar.NewTLV(oscar.OServiceTLVTagsSSLState, uint8(0x00)),
  360. },
  361. },
  362. },
  363. }, nil
  364. }
  365. // WriteOServiceHostOnline initiates the BOS protocol sequence.
  366. // It returns SNAC oscar.OServiceHostOnline containing the list food groups
  367. // supported by the BOS service.
  368. func (s OServiceServiceForBOS) WriteOServiceHostOnline() oscar.SNACMessage {
  369. return oscar.SNACMessage{
  370. Frame: oscar.SNACFrame{
  371. FoodGroup: oscar.OService,
  372. SubGroup: oscar.OServiceHostOnline,
  373. },
  374. Body: oscar.SNAC_0x01_0x03_OServiceHostOnline{
  375. FoodGroups: []uint16{
  376. oscar.Alert,
  377. oscar.Buddy,
  378. oscar.ChatNav,
  379. oscar.Feedbag,
  380. oscar.ICBM,
  381. oscar.Locate,
  382. oscar.OService,
  383. },
  384. },
  385. }
  386. }
  387. // ClientOnlineHandler runs when the current user is ready to join.
  388. // It performs the following sequence of actions:
  389. // - Announce current user's arrival to users who have the current user on
  390. // their buddy list.
  391. // - Send current user its buddy list
  392. func (s OServiceServiceForBOS) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *state.Session) error {
  393. if err := broadcastArrival(ctx, sess, s.messageRelayer, s.feedbagManager); err != nil {
  394. return err
  395. }
  396. buddies, err := s.feedbagManager.Buddies(sess.ScreenName())
  397. if err != nil {
  398. return err
  399. }
  400. for _, buddy := range buddies {
  401. unicastArrival(ctx, buddy, sess.ScreenName(), s.messageRelayer)
  402. }
  403. return nil
  404. }
  405. // NewOServiceServiceForChat creates a new instance of OServiceServiceForChat.
  406. func NewOServiceServiceForChat(oserviceService OServiceService, chatRegistry *state.ChatRegistry) *OServiceServiceForChat {
  407. return &OServiceServiceForChat{
  408. OServiceService: oserviceService,
  409. chatRegistry: chatRegistry,
  410. }
  411. }
  412. // OServiceServiceForChat contains handlers for the OService food group for the
  413. // Chat service.
  414. type OServiceServiceForChat struct {
  415. OServiceService
  416. chatRegistry *state.ChatRegistry
  417. }
  418. // WriteOServiceHostOnline initiates the Chat protocol sequence.
  419. // It returns SNAC oscar.OServiceHostOnline containing the list of food groups
  420. // supported by the Chat service.
  421. func (s OServiceServiceForChat) WriteOServiceHostOnline() oscar.SNACMessage {
  422. return oscar.SNACMessage{
  423. Frame: oscar.SNACFrame{
  424. FoodGroup: oscar.OService,
  425. SubGroup: oscar.OServiceHostOnline,
  426. },
  427. Body: oscar.SNAC_0x01_0x03_OServiceHostOnline{
  428. FoodGroups: []uint16{
  429. oscar.OService,
  430. oscar.Chat,
  431. },
  432. },
  433. }
  434. }
  435. // ClientOnlineHandler runs when the current user is ready to join the chat.
  436. // Trigger the following actions:
  437. // - Send current user the chat room metadata
  438. // - Announce current user's arrival to other chat room participants
  439. // - Send current user the chat room participant list
  440. func (s OServiceServiceForChat) ClientOnlineHandler(ctx context.Context, sess *state.Session, chatID string) error {
  441. room, chatSessMgr, err := s.chatRegistry.Retrieve(chatID)
  442. if err != nil {
  443. return err
  444. }
  445. sendChatRoomInfoUpdate(ctx, sess, chatSessMgr.(ChatMessageRelayer), room)
  446. alertUserJoined(ctx, sess, chatSessMgr.(ChatMessageRelayer))
  447. setOnlineChatUsers(ctx, sess, chatSessMgr.(ChatMessageRelayer))
  448. return nil
  449. }