| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- package foodgroup
- import (
- "context"
- "errors"
- "fmt"
- "log/slog"
- "github.com/mk6i/open-oscar-server/state"
- "github.com/mk6i/open-oscar-server/wire"
- )
- var defaultExchangeCfg = wire.TLVBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatRoomTLVMaxConcurrentRooms, uint8(10)),
- wire.NewTLVBE(wire.ChatRoomTLVClassPerms, uint16(0x0010)),
- wire.NewTLVBE(wire.ChatRoomTLVMaxNameLen, uint16(100)),
- wire.NewTLVBE(wire.ChatRoomTLVFlags, uint16(15)),
- wire.NewTLVBE(wire.ChatRoomTLVNavCreatePerms, uint8(2)),
- wire.NewTLVBE(wire.ChatRoomTLVCharSet1, "us-ascii"),
- wire.NewTLVBE(wire.ChatRoomTLVLang1, "en"),
- wire.NewTLVBE(wire.ChatRoomTLVCharSet2, "us-ascii"),
- wire.NewTLVBE(wire.ChatRoomTLVLang2, "en"),
- },
- }
- var (
- errChatNavRoomNameMissing = errors.New("unable to find chat name in TLV payload")
- errChatNavRoomCreateFailed = errors.New("unable to create chat room")
- errChatNavRetrieveFailed = errors.New("unable to retrieve chat room chat room")
- errChatNavMismatchedExchange = errors.New("chat room exchange does not match requested exchange")
- )
- // NewChatNavService creates a new instance of NewChatNavService.
- func NewChatNavService(logger *slog.Logger, chatRoomManager ChatRoomRegistry) *ChatNavService {
- return &ChatNavService{
- logger: logger,
- chatRoomManager: chatRoomManager,
- }
- }
- // ChatNavService provides functionality for the ChatNav food group, which
- // handles chat room creation and serving chat room metadata.
- type ChatNavService struct {
- logger *slog.Logger
- chatRoomManager ChatRoomRegistry
- }
- // RequestChatRights returns SNAC wire.ChatNavNavInfo, which contains chat
- // navigation service parameters and limits.
- func (s ChatNavService) RequestChatRights(_ context.Context, inFrame wire.SNACFrame) wire.SNACMessage {
- return wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.ChatNav,
- SubGroup: wire.ChatNavNavInfo,
- RequestID: inFrame.RequestID,
- },
- Body: wire.SNAC_0x0D_0x09_ChatNavNavInfo{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatNavTLVMaxConcurrentRooms, uint8(10)),
- wire.NewTLVBE(wire.ChatNavTLVExchangeInfo, wire.SNAC_0x0D_0x09_TLVExchangeInfo{
- Identifier: state.PrivateExchange,
- TLVBlock: defaultExchangeCfg,
- }),
- wire.NewTLVBE(wire.ChatNavTLVExchangeInfo, wire.SNAC_0x0D_0x09_TLVExchangeInfo{
- Identifier: state.PublicExchange,
- TLVBlock: defaultExchangeCfg,
- }),
- },
- },
- },
- }
- }
- // CreateRoom creates and returns a chat room or returns an existing chat
- // room. It returns SNAC wire.ChatNavNavInfo, which contains metadata for the
- // chat room.
- func (s ChatNavService) CreateRoom(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (wire.SNACMessage, error) {
- if err := validateExchange(inBody.Exchange); err != nil {
- s.logger.Debug("error validating exchange: " + err.Error())
- return sendChatNavErrorSNAC(inFrame, wire.ErrorCodeNotSupportedByHost)
- }
- if inBody.Cookie != "create" {
- s.logger.Info("got a non-create cookie", "value", inBody.Cookie)
- }
- name, hasName := inBody.String(wire.ChatRoomTLVRoomName)
- if !hasName {
- return wire.SNACMessage{}, errChatNavRoomNameMissing
- }
- // todo call ChatRoomByName and CreateChatRoom in a txn
- room, err := s.chatRoomManager.ChatRoomByName(ctx, inBody.Exchange, name)
- switch {
- case errors.Is(err, state.ErrChatRoomNotFound):
- if inBody.Exchange == state.PublicExchange {
- s.logger.Debug(fmt.Sprintf("public chat room not found: %s:%d", name, inBody.Exchange))
- return sendChatNavErrorSNAC(inFrame, wire.ErrorCodeNoMatch)
- }
- room = state.NewChatRoom(name, instance.IdentScreenName(), inBody.Exchange)
- if err := s.chatRoomManager.CreateChatRoom(ctx, &room); err != nil {
- return wire.SNACMessage{}, fmt.Errorf("%w: %w", errChatNavRoomCreateFailed, err)
- }
- case err != nil:
- return wire.SNACMessage{}, fmt.Errorf("%w: %w", errChatNavRetrieveFailed, err)
- }
- return wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.ChatNav,
- SubGroup: wire.ChatNavNavInfo,
- RequestID: inFrame.RequestID,
- },
- Body: wire.SNAC_0x0D_0x09_ChatNavNavInfo{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatNavTLVRoomInfo, wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
- Cookie: room.Cookie(),
- Exchange: room.Exchange(),
- DetailLevel: room.DetailLevel(),
- InstanceNumber: room.InstanceNumber(),
- TLVBlock: wire.TLVBlock{
- TLVList: room.TLVList(),
- },
- }),
- },
- },
- },
- }, nil
- }
- // RequestRoomInfo returns wire.ChatNavNavInfo, which contains metadata for
- // the chat room specified in the inFrame.hmacCookie.
- func (s ChatNavService) RequestRoomInfo(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (wire.SNACMessage, error) {
- if err := validateExchange(inBody.Exchange); err != nil {
- s.logger.Debug("error validating exchange: " + err.Error())
- return sendChatNavErrorSNAC(inFrame, wire.ErrorCodeNotSupportedByHost)
- }
- room, err := s.chatRoomManager.ChatRoomByCookie(ctx, inBody.Cookie)
- if err != nil {
- return wire.SNACMessage{}, fmt.Errorf("%w: %w", state.ErrChatRoomNotFound, err)
- }
- if room.Exchange() != inBody.Exchange {
- return wire.SNACMessage{}, errChatNavMismatchedExchange
- }
- return wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.ChatNav,
- SubGroup: wire.ChatNavNavInfo,
- RequestID: inFrame.RequestID,
- },
- Body: wire.SNAC_0x0D_0x09_ChatNavNavInfo{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatNavTLVRoomInfo, wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
- Cookie: room.Cookie(),
- Exchange: room.Exchange(),
- DetailLevel: room.DetailLevel(),
- InstanceNumber: room.InstanceNumber(),
- TLVBlock: wire.TLVBlock{
- TLVList: room.TLVList(),
- },
- }),
- },
- },
- },
- }, nil
- }
- func (s ChatNavService) ExchangeInfo(_ context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo) (wire.SNACMessage, error) {
- if err := validateExchange(inBody.Exchange); err != nil {
- s.logger.Debug("error validating exchange: " + err.Error())
- return sendChatNavErrorSNAC(inFrame, wire.ErrorCodeNotSupportedByHost)
- }
- return wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.ChatNav,
- SubGroup: wire.ChatNavNavInfo,
- RequestID: inFrame.RequestID,
- },
- Body: wire.SNAC_0x0D_0x09_ChatNavNavInfo{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatNavTLVMaxConcurrentRooms, uint8(10)),
- wire.NewTLVBE(wire.ChatNavTLVExchangeInfo, wire.SNAC_0x0D_0x09_TLVExchangeInfo{
- Identifier: inBody.Exchange,
- TLVBlock: defaultExchangeCfg,
- }),
- },
- },
- },
- }, nil
- }
- // sendChatNavErrorSNAC returns a ChatNavErr SNAC and logs an error for the operator
- func sendChatNavErrorSNAC(inFrame wire.SNACFrame, errorCode uint16) (wire.SNACMessage, error) {
- return wire.SNACMessage{
- Frame: wire.SNACFrame{
- FoodGroup: wire.ChatNav,
- SubGroup: wire.ChatNavErr,
- RequestID: inFrame.RequestID,
- },
- Body: wire.SNACError{
- Code: errorCode,
- },
- }, nil
- }
- func validateExchange(exchange uint16) error {
- if exchange != state.PrivateExchange && exchange != state.PublicExchange {
- return fmt.Errorf("only exchanges %d and %d are supported", state.PrivateExchange, state.PublicExchange)
- }
- return nil
- }
|