| Server IP : 157.230.181.24 / Your IP : 216.73.217.11 Web Server : Apache/2.4.58 (Ubuntu) System : Linux conductive 6.8.0-117-generic #117-Ubuntu SMP PREEMPT_DYNAMIC Tue May 5 19:26:24 UTC 2026 x86_64 User : ( 1000) PHP Version : 8.3.31 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/vhosts/ceagon/open-oscar-server/foodgroup/ |
Upload File : |
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
}